# `AshAuthentication.Strategy.OAuth2.UserResolver`
[🔗](https://github.com/team-alembic/ash_authentication/blob/main/lib/ash_authentication/strategies/oauth2/user_resolver.ex#L5)

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.

## Why the link rule compares the email

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 {: .info}
>
> 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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
