Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions docs/backends/google.rst
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,11 @@ User identification

Google OAuth2, OpenID Connect, and One Tap use the stable ``sub`` claim for
account association. The legacy OAuth1 backend uses Google's stable ``id``.
Associations created by older social-core releases used the email address and
migrate to the stable identifier on the next successful authentication.
Older default setups used email addresses. Historical associations can migrate
through indexed email lookups, subject to the policy in
:doc:`../configuration/identifier-migration`. Google retains the default
unverified compatibility allowance only for its audited email-to-stable-ID
transitions; conflicting stored evidence always blocks migration.

The following legacy settings remain accepted, but stable identifiers are now
the default::
Expand All @@ -166,7 +169,10 @@ or::

SOCIAL_AUTH_GOOGLE_OAUTH2_USE_UNIQUE_USER_ID = True

depending on the backends in use.
depending on the backends in use. If these settings were already enabled before
identifier-key tracking, configure the Django backfill's
``SOCIAL_AUTH_OLD_ID_KEYS`` with ``sub`` for OAuth2, OpenID Connect, and One Tap,
or ``id`` for OAuth1. The historical default backfill otherwise uses ``email``.
Comment on lines +172 to +175

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Name the backend-specific Google legacy settings

For OpenID Connect and One Tap, the two settings shown above are not the applicable flags because settings are scoped by backend name; historical configurations use SOCIAL_AUTH_GOOGLE_OPENIDCONNECT_USE_UNIQUE_USER_ID and SOCIAL_AUTH_GOOGLE_ONETAP_USE_UNIQUE_USER_ID. Referring to “these settings” while including those backends can lead an operator to conclude that the OAuth2 flag affected them and incorrectly backfill email-based rows as sub, after which indexed lookup will no longer find those associations. List each backend-specific legacy setting or restrict this instruction to the two displayed backends.

Useful? React with 👍 / 👎.


See `Configurable User ID Key`_ for migration controls and custom identifier
settings.
Expand Down
16 changes: 16 additions & 0 deletions docs/backends/implementation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,22 @@ overridden to customize behavior. Here are some key methods:
special logic for determining the ID key. Instead, use the
``SOCIAL_AUTH_<BACKEND_NAME>_ID_KEY`` setting to configure it.

``get_legacy_user_identifiers(details, response)``
Returns previous ``(id_key, uid)`` pairs from built-in ``LEGACY_ID_KEYS`` and
backend-scoped configuration. Missing claims are omitted; other provider
response errors propagate. Override this hook when historical identifiers
need special extraction or scoping. ``get_legacy_user_ids()`` remains a
compatibility wrapper returning values only. The pipeline invokes both
hooks and uses additional values from existing custom overrides to find
empty-key associations, subject to the same evidence requirements. Prefer
the keyed hook for new overrides and associations with recorded keys.

``get_stored_user_id_keys(id_key)``
Returns stored extra-data fields containing evidence for the current
identifier. The default returns ``(id_key,)``. Historical OIDC subjects may
also be stored under ``id``. Only declare aliases known to represent the
same identifier; every present evidence field must agree.

``get_user_id(details, response)``
Returns a unique ID for the current user from the provider's response or
from the details dict. This method uses ``id_key()`` to determine which
Expand Down
9 changes: 9 additions & 0 deletions docs/backends/keycloak.rst
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,12 @@ External memberships

See :doc:`/groups` for opt-in extraction, group-based login restrictions, and
local group synchronization. No separate extraction pipeline step is needed.


Changing account identifier configuration
-----------------------------------------

For explicit identifier changes, configure previous ``LEGACY_ID_KEYS`` and
ensure the new stable identifier was stored before changing ``ID_KEY``.
See :doc:`../configuration/identifier-migration`. Merely recording the old key
or presenting a matching username does not prove ownership of the association.
37 changes: 32 additions & 5 deletions docs/configuration/django.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,38 @@ or applying your initial migrations. See :ref:`django-user-models`.

python manage.py migrate

Upgrades that add identifier-key tracking must install compatible releases of
both ``social-auth-core`` and ``social-auth-app-django`` before running this
command. The Django migration adds a blank ``id_key`` to existing social
associations; social-core then migrates those rows according to the policy in
:ref:`the configurable user ID key documentation <configurable-user-id-key>`.
Install compatible releases of both ``social-auth-core`` and
``social-auth-app-django`` before running this command. Indexed identifier
migration requires social-auth-core 6.1 or later. A data migration backfills
empty ``id_key`` values using frozen pre-v7 backend defaults; it preserves UIDs,
existing keys, extra data, and timestamps.

If your previous setup used custom identifiers, configure
``SOCIAL_AUTH_OLD_ID_KEYS`` **before running migrations**::

SOCIAL_AUTH_OLD_ID_KEYS = {
"google-oauth2": "sub", # Previously used USE_UNIQUE_USER_ID=True.
"custom-provider": "old_subject",
"trello": None, # Leave this provider unchanged.
}

Each entry describes the historical setup, not the current configuration.
Unlisted known providers use frozen defaults. Unknown providers remain
unchanged. A nonempty key must fit the model's ``id_key`` field; ``None`` skips
that provider. Invalid overrides fail before any updates.

The migration executes one SQL update per provider without loading associations
into Python. It commits each provider separately and can resume after failure
without overwriting completed backfills. Each large update can still hold locks
and generate substantial transaction logs; schedule it during your deployment
maintenance window. Reversing the migration preserves identifier metadata.
Changing overrides after it has completed does not rerun it or rewrite populated
keys; review incorrect historical mappings before serving authentication.

Backfilling a key does not prove that a mutable identifier still belongs to the
same account. Read :doc:`identifier-migration` for the migration policy,
configuration changes, and recovery requirements. Run migrations before serving
upgraded authentication traffic.

**8. Add login form in template**::

Expand Down
239 changes: 239 additions & 0 deletions docs/configuration/identifier-migration.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
.. _identifier-migration:

Identifier migration
====================

Authentication looks up the current ``(provider, id_key, uid)`` first. If it
does not exist, it looks up previous keys and their values from the authenticated
provider response. These queries use the existing provider/UID index; they do
not search every association's JSON data. Empty-key historical rows are also
considered using the same indexed UID lookups.

An old email or username locates a candidate, but does not prove that the
candidate belongs to the current provider account. The candidate's saved current
identifier must match the authenticated identifier. Multiple candidates,
conflicting evidence, and invalid evidence stop authentication. Missing evidence
also stops authentication unless the compatibility policy below permits it.
Migration updates both identifier fields atomically and revalidates evidence
under the association lock.

Configuration changes
---------------------

Before switching identifiers, configure extra data to save the proposed new
stable field while the old key is still active. Existing users must authenticate
in this configuration, or receive an independently verified administrative
backfill, before their associations contain that evidence. Adding an extra-data
setting does not populate historical rows automatically.

For example, while Google still uses email::

SOCIAL_AUTH_GOOGLE_OAUTH2_ID_KEY = "email"
SOCIAL_AUTH_GOOGLE_OAUTH2_EXTRA_DATA = [("sub", "sub")]

Then switch the active key and retain the historical key::

SOCIAL_AUTH_GOOGLE_OAUTH2_ID_KEY = "sub"
SOCIAL_AUTH_GOOGLE_OAUTH2_LEGACY_ID_KEYS = ["email"]
SOCIAL_AUTH_GOOGLE_OAUTH2_ALLOW_UNVERIFIED_LEGACY_UID_MIGRATION = False

Configured historical keys supplement the backend's built-in keys, and work
with an explicit current ``ID_KEY``. Custom backends must implement extraction
for their historical keys when identifiers require transformations or scoping.

If an email or username has changed and its old value is no longer returned,
indexed lookup cannot recover the old association. There is no global JSON
search fallback. Use account recovery or an authenticated linking flow.
Presenting a reclaimed mutable identifier with conflicting stored evidence
always fails, including when unverified migration is enabled.

Compatibility policy
--------------------

``ALLOW_UNVERIFIED_LEGACY_UID_MIGRATION`` defaults to enabled only for the
specific built-in transitions marked "Yes" below. It requires the audited
backend class and current built-in key. Custom subclasses, newly added legacy
keys, and other configuration-driven transitions require evidence by default.
Explicitly selecting the same built-in current key retains that transition's
default. Other current keys do not.

The global ``SOCIAL_AUTH_ALLOW_UNVERIFIED_LEGACY_UID_MIGRATION`` setting and its
backend-specific variants override that default. Explicit ``False`` always
requires evidence. Explicit ``True`` permits missing evidence even outside the
audited list, but cannot override conflicting or invalid evidence.

.. warning::

Allowing missing evidence accepts the risk that a new owner of an old email,
username, or other mutable identifier can claim its association. The
compatibility policy preserves that behavior for known historical setups;
it does not make those migrations safe. Disable it before upgrading if this
risk is unacceptable.

.. _historical-identifier-audit:

Historical backend audit
------------------------

This table describes default storage declarations in social-core 5.2.0 for the
25 built-in backend variants that now declare historical identifier keys.
Three stored the new identifier directly; Fence and CAS stored ``sub`` under
``id``. The remaining 20 lacked a directly usable stable identifier field.
Stored ID tokens are not decoded as migration evidence. Actual data can differ
because of older releases, custom pipelines, or extra-data configuration.

.. list-table:: Historical transitions and default compatibility allowances
:header-rows: 1
:widths: 25 18 18 22 17

* - Provider
- Old key
- Current key
- Stored evidence
- Unverified default
* - ``arcgis``
- ``username``
- ``id``
- Absent
- Yes
* - ``azuread-oauth2``
- ``upn``
- ``sub``
- Absent
- Yes
* - ``azuread-oauth2-v2``
- ``upn``
- ``sub``
- Absent
- Yes
* - ``azuread-v2-tenant-oauth2``
- ``preferred_username``
- ``sub``
- Absent
- Yes
* - ``cas``
- ``username``
- ``sub``
- Alias ``id``
- No
* - ``cognito``
- ``username``
- ``sub``
- Absent
- Yes
* - ``dailymotion``
- ``username``
- ``id``
- ``id``
- No
* - ``deezer``
- ``name``
- ``id``
- Absent
- Yes
* - ``discourse``
- ``email``
- ``external_id``
- Absent
- Yes
* - ``fence``
- ``username``
- ``sub``
- Alias ``id``
- No
* - ``google-oauth``
- ``email``
- ``id``
- Absent
- Yes
* - ``google-oauth2``
- ``email``
- ``sub``
- Absent
- Yes
* - ``google-onetap``
- ``email``
- ``sub``
- Absent
- Yes
* - ``google-openidconnect``
- ``email``
- ``sub``
- Absent
- Yes
* - ``keycloak``
- ``username``
- ``sub``
- Absent
- Yes
* - ``mailru``
- ``email``
- ``id``
- Absent
- Yes
* - ``okta-oauth2``
- ``preferred_username``
- ``sub``
- Absent
- Yes
* - ``okta-openidconnect``
- ``preferred_username``
- ``sub``
- Absent
- Yes
* - ``opensuse``
- ``nickname``
- ``identity_url``
- Absent
- Yes
* - ``qiita``
- ``id``
- ``permanent_id``
- ``permanent_id``
- No
* - ``scistarter``
- ``email``
- ``profile_id``
- ``profile_id``
- No
* - ``trello``
- ``username``
- ``id``
- Absent
- Yes
* - ``tumblr``
- ``name``
- ``uuid``
- Absent
- Yes
* - ``ubuntu``
- ``nickname``
- ``identity_url``
- Absent
- Yes
* - ``yandex-openid``
- ``email``
- ``identity_url``
- Absent
- Yes

Django historical backfill
--------------------------

The Django data migration records historical keys using frozen pre-v7 defaults.
It changes neither UIDs nor account ownership. Configure
``SOCIAL_AUTH_OLD_ID_KEYS`` before running it if previous settings selected a
different identifier. This includes Google ``USE_UNIQUE_USER_ID`` and Qiita
``IDENTIFIED_BY_PERMANENT_ID``. For example::

SOCIAL_AUTH_OLD_ID_KEYS = {
"google-oauth2": "sub",
"qiita": "permanent_id",
"custom-provider": "old_subject",
"trello": None,
}

Unknown and skipped providers retain empty keys. Authentication considers them
only through indexed UID candidates and the same evidence policy. A historical
key describes how existing associations were created; current settings alone
cannot establish it. See :doc:`django` for deployment order and migration costs.
1 change: 1 addition & 0 deletions docs/configuration/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Contents:
:maxdepth: 2

settings
identifier-migration
django
flask
pyramid
Expand Down
Loading
Loading