У нас используется следующая схема таблиц:
Table "public.social_account"
Column | Type | Modifiers
-------------+--------------------------------+-----------
user_id | integer | not null
network | character varying(255) | not null
external_id | character varying(255) | not null
created_at | timestamp(0) without time zone | not null
updated_at | timestamp(0) without time zone | not null
id | uuid | not null
Indexes:
"social_account_pkey" PRIMARY KEY, btree (id)
"unique_social_account" UNIQUE, btree (network, external_id)
"idx_f24d8339a76ed395" btree (user_id)
Foreign-key constraints:
"fk_f24d8339a76ed395" FOREIGN KEY (user_id) REFERENCES app_user(id) ON DELETE CASCADE
То есть для каждой привязки хранится запись, в которой содержится user_id, название соцсети (google/vk/facebook/etc) и id этой соцсети (у всех произвольный формат).
Таким образом, когда пользователь аутентифицируется через одну из этих соцсетей, сначала ищется user_id по связке external_id + network. Если user_id найден - аутентифицируем текущего пользователя как этот user_id. Если нет, получаем от соцсети email и по нему ищем пользователя в таблице пользователей. Если нашли, то создаем запись в social_account и аутентифицируем юзера. Если не нашлось ничего (первый визит), то создаем пользователя и создаем запись в social_account.