AshAuthentication.Strategy.OAuth2.UserResolver (ash_authentication v5.0.0-rc.15)

Copy Markdown View Source

Resolves which local user an OAuth2/OIDC sign-in belongs to.

Per OpenID Connect Core only the iss/sub claim combination uniquely and stably identifies an end-user, so matching is driven by the user identity resource - never by the email address.

Given the changeset for an OAuth2/OIDC register (upsert) action, the rules are:

  1. If an identity already exists for this (strategy, sub), the sign-in belongs to that user. The changeset's upsert keys are rewritten to that user's values so the upsert resolves to them (and the provider cannot change a user's email).

  2. Otherwise (a sub not seen before):

    • If no local account matches the register action's upsert_identity - proceed (a new account is created; if the email is not trusted and a confirmation add-on is present, that add-on gates it).
    • If the matched account already has an identity for this strategy (a different sub) - reject. A single account cannot have two identities for the same provider auto-linked.
    • If the strategy's email_verified claim can be trusted (trust_email_verified? and the claim is true) and the account was matched by that verified email - link the sign-in to that account.
    • Otherwise the email cannot be trusted to prove ownership of the matched account. With on_untrusted_email_match :reject (the default) the sign-in is rejected and the user must sign in with their existing method to link the provider. With on_untrusted_email_match :confirm the upsert is aborted with a ConfirmationRequired error so the caller can issue a confirmation to the existing account's email and link the provider only once the recipient proves ownership.

An upsert_identity is not necessarily the email field - it can be a username or any other claim the provider supplies, and the strategy has no setting that names which attribute holds the email. A trusted email_verified claim attests only that the person signing in owns some email, so on its own it says nothing about an account matched on a username.

The link rule therefore requires the verified email to be the value that matched the account. The match is what carries the evidence: an account found by a value equal to the email the provider attested holds that email by construction of the query. The premise stops being an assumption and becomes a tested fact, with no need to know which attribute the email lives in.

Accounts matched on an email-shaped identifier

If the matched value is itself an email address - an account whose username is an email, say - a sign-in that verifies that same address still links. The person signing in has proven they own the identifier the account is keyed on, so they can generally already take the account by password reset. Use an email-keyed upsert_identity if you want the two to stay distinct.

A registration_enabled? false strategy has no upsert and no matched keys, so it cannot test the premise this way. AshAuthentication.Strategy.OAuth2.SignInPreparation applies the same rule against the strategy's email_field instead.

Rejections are surfaced as a generic AuthenticationFailed error to avoid leaking which email addresses are registered.