Account linking lets a user add another way to sign in to an existing account (Google, enterprise SSO, a passkey). The linking decision is an authorization decision: a linked method can do everything the original login can. A system that makes that decision on a matching email address can bind a stranger's identity to an account they never owned. This post documents a safe default for binding login methods and its trade-offs.

System description

Each account keeps a list of login methods. A method is a record that binds a provider's issuer and stable subject to the account; sign-in resolves accounts through that binding. Adding a method requires a fresh authentication of the account it joins.

Designing a Safe Account Linking Flow

Architecture choice

There are two common places to make the linking decision.

The user starts from a signed-in session, re-authenticates, completes the provider round trip, and the server writes the binding.

Use this when:

  • Users can self-register with arbitrary email addresses

  • More than one identity provider is enabled

  • Tenants can bring their own SSO

Trade-off: the user has to re-authenticate before linking. Users who forgot their original password need recovery before they can link, which pushes traffic into the reset flow.

Automatic linking on a verified email match

A sign-in through a new provider whose verified email matches an existing account links silently and logs the user in.

Use this when:

  • Every enabled provider is authoritative for the addresses it asserts (the provider hosts the mailbox domain)

  • Signup friction costs you more than the residual takeover risk

  • Every silent link produces a notification the account owner will actually see

Trade-off: you inherit each provider's definition of "verified", and the definitions differ. A provider that asserts addresses it never confirmed can sign in as any matching account in your system.

Common middle ground: automatic linking only where the provider hosts the asserted domain (a Gmail address asserted by Google), explicit linking for everything else.

Golden path

Re-authenticate the current account → verify the new provider's assertion → extract the issuer and stable subject → reject the link if that pair is already bound elsewhere → write the login method → notify the account's existing channels → record the link event

Related patterns:

Core design

Login-method record (data plane)

Minimum fields:

  • method_id: opaque identifier

  • account_id: the account this method signs into

  • provider_id: reference to the provider configuration that verified the assertion

  • issuer: the iss value (or SAML entityID) the assertion was validated against

  • subject: the provider's stable identifier (sub, persistent NameID, or the provider's documented equivalent)

  • email_hint: the address asserted at link time, display only

  • binding_source: self_service, directory, or tenant_claimed

  • created_at, last_used_at

(issuer, subject) carries a unique constraint across all accounts. Sign-in resolves the account by that pair; email_hint never participates in the lookup. The pair matches what OpenID Connect Core (section 5.7) defines as the only stable identifier a relying party can depend on.

Subject scope varies by provider

A subject is only stable within the scope its provider defines. Store the field each provider documents as durable, and record which field was chosen on the provider configuration:

  • Google: sub, one global value per account

  • Apple: sub, scoped to your developer team; if the app moves to another team, Apple rotates subjects through a migration claim

  • Microsoft Entra: sub is scoped to one application registration; to recognize the same user across your own apps, store the tenant id plus object id

  • GitHub (OAuth, no ID token): the numeric account id

  • SAML: the NameID with Format set to persistent; treat a format change as a new identity

Assertion verification

The verifier resolves signing keys only for issuers pinned in provider configuration, and compares the token's iss against the configured value. For multi-tenant issuers, the tenant id in the token is validated against an allowlist on the provider config. Claims with documented type variance (Apple's email_verified arrives as a boolean or as the string "true") go through a typed parser per provider profile; an unparseable claim reads as unverified.

  1. The request requires a session that re-authenticated within the last few minutes

  2. The server generates state and nonce bound to both the session and this specific request

  3. On callback, the target account comes from the session

  4. The conflict check and the insert run in one transaction against the unique constraint

  5. Notification goes to every previously existing channel on the account, naming the provider and the asserted address

Tenant policy on login methods

binding_source records who controls an account's login methods. On a directory account (provisioned through SCIM, for example), the tenant's provisioning system owns them: the user cannot add or remove methods, and email changes come from the directory.

Minimal API shape

GET    /account/login-methods           → [{ method_id, provider, email_hint, created_at }]
POST   /account/login-methods           → 302 to provider (requires fresh re-authentication)
POST   /account/login-methods/callback  → 204; account derived from session
DELETE /account/login-methods/{id}      → 204 (requires fresh re-authentication)

Threat model

Baseline assumptions

  • Clients are untrusted: they can replay callbacks and claim any address in a request body

  • Providers are semi-trusted: signatures and transport are sound, but a provider's claims about addresses on domains it does not host carry no authority

  • The control plane derives the acting account from the session, not from any assertion or request field

  • Provider signing keys are fetched over verified channels from configured endpoints

  • Standard infra controls such as TLS, CSRF protection on callbacks, and secret management are assumed to be in place. This model focuses on the binding decision: which external identity may attach to which account

A note on risk

This table is not a checklist. Focus on preventing the highest-impact failures first. Detection and response are acceptable where prevention is impractical.

Phase 1: Adding a method

Focus: Ensuring only the account owner can attach a new identity

Asset

Threat

Baseline Controls

Mitigation Options

Risk

Account ownership

Email-match takeover: Attacker authenticates at a provider that asserts the victim's address without verifying it, or that reports verification in a type the parser misreads, and the system attaches the attacker's identity to the victim's account because the addresses match

None

1. Never link from an email match alone; require an authenticated session on the target account plus fresh re-authentication

2. Key the binding on issuer and subject; keep the address as a display hint

3. Typed comparison per provider profile; treat any other value as unverified

High

Link state

Pre-hijack seeding: Attacker creates the account, or parks a pending link, on an address the victim will later prove ownership of; the planted method survives the victim's signup or reset

Email verification at signup

1. Void pending links and unverified methods whenever the address is verified by a different session

2. Surface the method list in security settings and notify on every addition

3. Expire pending link state in minutes

Medium

Link request

Forced link: Attacker finishes the provider round trip with their own account, then gets the victim to open the callback URL; the victim's signed-in session completes the link and the attacker's identity is added to the victim's account

State parameter checked

1. Accept a callback only from the session that started the link request; reject state issued to any other session

2. Consume state and nonce on first use

3. Show a confirmation naming the provider and asserted address before the write

Medium

Phase 2: Sign-in resolution

Focus: Keeping the stored binding the only path from an assertion to an account

Asset

Threat

Baseline Controls

Mitigation Options

Risk

Issuer binding

Issuer confusion: The verifier fetches signing keys from whatever issuer the token names, so an attacker who runs their own OIDC issuer can mint a token asserting the victim's email, sign it with their own key, and pass validation

Signature verification

1. Pin issuers in provider configuration and resolve keys only for pinned values

2. Exact-match iss against the configured value; validate templated tenant ids against the allowlist

3. Periodically audit stored issuer values against the configured set

High

Existing accounts

JIT takeover: JIT provisioning finds an existing account whose email matches the SSO assertion and attaches the SSO identity to it; an admin of any tenant on the platform who can configure an IdP can assert a victim's email and sign in to the victim's account

JIT provisioning creates missing accounts

1. Let JIT create accounts only; on an email match with an existing account, fail and route to an explicit link flow

2. Scope each SSO connection to the domains the tenant has verified

3. Offer a per-connection setting that turns JIT off

High

Login lookup

Recycled identifier: The provider reassigns an address or username to a different person, and a lookup that falls back to email resolves the new holder to the old owner's account

Subject lookup on the primary path

1. Remove the email fallback; a subject with no binding is a new identity, whatever the address says

2. For providers without stable subjects, use the documented numeric or opaque id rather than the handle

3. Flag sign-ins to dormant accounts for step-up verification

Medium

Phase 3: Lifecycle and policy

Focus: Keeping the method list consistent with tenant policy and recoverable ownership

Asset

Threat

Baseline Controls

Mitigation Options

Risk

Tenant policy

Policy bypass: A user under enforced tenant SSO adds a personal login or password, keeping a path open that the tenant cannot see or revoke

SSO required at the login prompt

1. Block self-service method changes for directory accounts

2. Evaluate domain policy at link time as well as at the prompt

3. On enforcement, disable existing non-SSO methods and revoke their sessions and API tokens rather than hiding the buttons

Medium

Method removal

Removal abuse: A hijacked session strips the owner's methods, or a legitimate removal leaves the account with no strong method that recovery accepts

Authenticated session required

1. Fresh re-authentication for removal; refuse to remove the last method recovery can verify

2. Notify every channel on removal and keep a short undo window

3. Record removals with the same fields as links

Low

Audit trail

Attribution gap: The event says a provider was linked but not which issuer, subject, actor, or tenant, so a malicious link is indistinguishable from a legitimate one later

Event logging exists

1. Record issuer, subject reference, acting session, and tenant on every change

2. Retain across disclosure windows (months)

3. Alert on links to dormant or high-privilege accounts

Low

  • Your effective verification standard becomes the weakest flow among the providers you enable

  • Restrict the shortcut to providers that host the asserted domain; assertions about domains the provider does not control drop back to explicit linking

  • A silent link makes the notification the only detection control; send it to channels that existed before the link

  • Exclude directory and SSO-governed accounts from auto-linking entirely

FAQs

Is a verified email from the provider enough to link automatically?

Not by itself. email_verified records that the issuer confirmed the address at some point in the past; it says nothing about whether that issuer has any authority over the domain. Auto-linking is defensible when the provider hosts the mailbox it asserts (Yahoo asserting a yahoo.com address): that assertion proves control of the mailbox itself, the same proof your own confirmation email would produce. The unsafe case is a provider asserting an address on a domain it does not host; anyone can register an account there with someone else's address typed in.

What about merging two accounts that belong to the same person?

Merging is a different operation. Linking adds a credential to one account; merging moves data and memberships between two accounts, and a wrong merge is hard to undo. Build merge as its own flow: require proof of control of both accounts and keep the migration reversible.

Verification checklist

  • Binding keys

    • The same email asserted by a different provider subject does not resolve to or link with the existing account

    • Sign-in resolution queries (issuer, subject) only; deleting email_hint from every record breaks nothing

    • A second link attempt for an already-bound (issuer, subject) pair fails against the unique constraint, with a generic error, and the existing account keeps its binding

    • Changing the email at the provider changes email_hint at most; account resolution is unaffected

  • Link flow

    • A link attempt on a session older than the re-authentication window is rejected

    • An assertion with email_verified absent, false, or the string "false" is treated as unverified

    • The callback ignores any account or email identifier in the request; the target account comes from the session

    • State and nonce are single-use; a replayed callback fails

    • A callback opened in a session other than the one that started the link request is rejected

  • Issuer discipline

    • A validly signed token from an issuer outside the configured set is rejected before any account lookup

    • For multi-tenant issuers, a token with an unlisted tenant id is rejected even though the signature and issuer template match

    • An audit query listing distinct stored issuer values returns only configured providers

  • Tenant policy

    • A directory account cannot add or remove a login method through self-service endpoints

    • Enforcing SSO on a tenant disables existing password and social methods, and the disabled methods no longer authenticate

    • API tokens that survive SSO enforcement are enumerable by the tenant admin

  • Lifecycle and audit

    • Removing the last recovery-capable method is refused

    • Every link and unlink event carries issuer, subject reference, acting session, tenant, and timestamp

    • A link on a dormant account raises an alert

Implementation & Review

The full threat model matrix, architectural diagrams, and a printable verification checklist for this pattern are available in the Secure Patterns repository. Use these artifacts to guide your design reviews and internal audits.

Keep Reading