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.
Explicit link with re-authentication
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 eventRelated patterns:
Designing a Safe Team Invitation Flow treats the invitation link as proof of possession and defers identity to the login flow; this post is that identity layer
If linking is reachable from recovery, read Password Reset Flows: The Secure Implementation Guide first; recovery creates the sessions the link flow trusts
For binding and uniqueness checks inside a single credential ceremony, see Passkey Authentication: Architecting a Secure Relying Party; this post applies the same discipline one level up, across providers
Core design
Login-method record (data plane)
Minimum fields:
method_id: opaque identifieraccount_id: the account this method signs intoprovider_id: reference to the provider configuration that verified the assertionissuer: theissvalue (or SAML entityID) the assertion was validated againstsubject: the provider's stable identifier (sub, persistent NameID, or the provider's documented equivalent)email_hint: the address asserted at link time, display onlybinding_source:self_service,directory, ortenant_claimedcreated_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 accountApple:
sub, scoped to your developer team; if the app moves to another team, Apple rotates subjects through a migration claimMicrosoft Entra:
subis scoped to one application registration; to recognize the same user across your own apps, store the tenant id plus object idGitHub (OAuth, no ID token): the numeric account id
SAML: the NameID with
Formatset topersistent; 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.
The link flow (control plane)
The request requires a session that re-authenticated within the last few minutes
The server generates state and nonce bound to both the session and this specific request
On callback, the target account comes from the session
The conflict check and the insert run in one transaction against the unique constraint
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 3. Periodically audit stored | 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 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 |
If you auto-link on a verified email match
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
directoryand 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; deletingemail_hintfrom every record breaks nothingA 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 bindingChanging the email at the provider changes
email_hintat 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_verifiedabsent, false, or the string"false"is treated as unverifiedThe 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
issuervalues returns only configured providers
Tenant policy
A
directoryaccount cannot add or remove a login method through self-service endpointsEnforcing 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.
