Your booking tool links a federated sign-in onto an existing password account whenever the identity provider asserts email_verified — what makes that an account-takeover path?
answer
- whose policy verified that address
- the inbound flow proves nothing local
- affirmative steps by the issuer's own standards
- prove the old account, then attach
- auto-link only inside a verified domain
basics
~20 sNothing in that sign-in proves control of the account that already holds the data. A verified-address claim reports only that the asserting party took affirmative steps by its own policy, so auto-linking on it hands an existing account to whoever that party will vouch for.
solid answer
~60 sThe existing account was created under your rules and its holder proved control of a credential you issued. The arriving identity proved control of something at somebody else's provider, and `email_verified` tells you only that the issuer took affirmative steps, by its own standards, to check the address at some point — OpenID Connect leaves the means to the agreement between the parties. So the claim is a report of someone else's policy, not evidence you can inspect, and matching on it joins a stranger's identity to two years of bookings, approvals and permissions without the password ever appearing. Replace it with an interactive link: the person is already signed in to the existing account, deliberately starts the link, completes the federated flow, and the identity row is written only because both proofs are present in one short-lived, server-side link context. If you must auto-link, narrow it to an issuer you onboarded that is authoritative for that address domain, and record every row linked that way so an incident can find them.
code
json · 6 lines{
"iss": "https://idp.institute.example/",
"sub": "a3f9c1d8-7b20-4e6a-9f11-2c5d8e0b4471",
"email": "[email protected]",
"email_verified": true
}go deeper
Remember the order: a federated sign-in proves who the arriving identity is at its provider, and proves nothing about an account that already exists in your product.
Explain what the verified-address claim actually asserts — affirmative steps by the issuer under its own policy — and why that makes an automatic join a delegation of your authentication decision.
Walk the takeover end to end and then design the interactive link: proof of the existing account first, a short-lived server-side link context, the identity row written only when both halves are present, and every automatic link recorded as such.
Frame it as which parties you will let nominate your accounts, and what onboarding a new connection therefore costs. Decide the policy once, scope it to verified domains, and make the automatic links enumerable so a provider's lapse is a query rather than an audit.
## What the claim actually says OpenID Connect Core 1.0 defines `email_verified` as `true` meaning the issuer took affirmative steps to ensure the address was under the control of the end-user at the time of verification — and says plainly that the means are up to the issuer, with the precise requirements a matter for the agreement between the parties relying on the claim. Read that twice. The specification does not define a check. It defines a report about a check somebody else says they did, under rules you may never have seen. SAML 2.0 does not even offer the boolean: an address arrives as an attribute, with whatever the attribute-release agreement says behind it and nothing in the assertion itself distinguishing a verified address from a typed one. So the question is not *is the claim trustworthy*. It is *whose policy are you making your authentication decision under*. Auto-linking on it is a standing delegation: any party you connect may nominate which of your existing accounts an arriving stranger becomes. ## The asymmetry that makes it a takeover Put the two sides of the join next to each other: | | The account that already exists | The identity that just arrived | |---|---|---| | Created under | your admission rules, two years ago | the issuer's rules, possibly minutes ago | | Proved control of | a credential you issued and can audit | something at the issuer you cannot inspect | | Carries | bookings, approvals, history, permissions | a subject and some attributes | | Verified by you | yes, at every sign-in since | never | An automatic join on a shared address treats the second column as equivalent to the first. It is not, and the practical routes in are dull rather than exotic: - an issuer that lets a self-service tenant assert its own address and marks it verified - an address recycled inside the institute's enterprise directory after its previous holder left, now asserted for a new person - a domain that lapsed and was re-registered, so a provider legitimately asserts addresses at it for strangers - a connection an administrator added for convenience — every issuer you accept is now able to name any account whose address it can guess The result is that the attacker signs in normally, your tool matches the address, writes an identity row, and hands over whatever the account is authorised to do. The password was never tried, so nothing in your failed-login telemetry fires. ## What replaces it An **interactive link** — and the direction is the whole point. The person must arrive carrying proof of the *old* account, not only of the new identity: 1. They are authenticated to the existing account in the current browser session and choose to add a sign-in route. 2. You demand fresh proof of control of that account before you start — how that proof is obtained is a separate mechanism, but the link must not ride on a session that has been open since morning. 3. You open a short-lived, server-side link context bound to that account and to the flow you are about to start. 4. The federated flow completes and returns an identity. 5. The row is written only because the link context is still open, still bound to that account, and the identity is not already linked to another one. The moment you stop treating the link as something the *inbound* flow may perform on its own, the takeover disappears — because the attacker cannot supply the proof in step 2. ## When automatic linking is defensible It is not never. It is narrow, and the boundaries are the honest part of the answer: - **One issuer, one domain, both verified with the customer.** You onboarded the institute's provider and confirmed it is authoritative for that address domain. - **The claim must be present and true.** An absent or false `email_verified` is an unverified address and is not eligible, ever. - **Never on a name, a display name or a partial match.** Those are not identifiers at all. - **Record the method.** Every automatically linked row is marked as such, so that when a provider turns out to have been lax you can enumerate exactly which accounts were joined that way instead of guessing. - **On a mismatch, do not guess.** An identity that matches nothing is a new arrival, and creating a duplicate account is an annoyance; a wrong join is an incident you may not be able to unwind. ## The thing candidates get backwards An interactive link is not a second factor and it is not a re-verification of the address. It is a demand for proof of control of the account that already holds the data — the one thing the inbound assertion, however well signed, can never supply.
- The institute's provider is the only one connected, and it is authoritative for the whole address domain. Is auto-linking safe now?Much safer, and still worth bounding. Scope it to that issuer and that domain, require the verified flag, mark every row as automatically linked, and keep the interactive path for everything outside the scope. The residual risk is an address recycled inside the institute's directory after someone leaves, so the narrower scope buys you an enumerable blast radius rather than a guarantee.
- What should happen when an arriving identity matches no account at all?Treat it as a new arrival and let the admission rules decide, rather than hunting for a near match on a name or a partial address. A duplicate account is a support ticket someone can fix later; a wrong join gives a stranger history and permissions, and may not be unwindable.
- Why is the interactive link's proof about the existing account rather than the new identity?Because the federated flow already proves the new identity — that is all it can prove. The gap it cannot close is whether the person driving it is the holder of the account you are about to attach it to, and only a fresh proof against that account closes it.
- What does logging the link decision need to capture to be useful in an incident?Which issuer asserted the identity, the subject, the address it matched on, which rule fired (automatic or interactive), who was signed in at the time, and when. With those you can answer 'which accounts did this provider get given' in one query; without them the investigation is a guess across every account with a matching domain.
A conference desk that hands over a locked sample cabinet to anyone whose badge shows the same surname as the person who rented it. The badge is genuine and the printer checked something before printing it — but the desk is now enforcing the badge printer's policy, not its own, and it never asked the arrival to show that they hold the cabinet's own key.
saying these in an interview costs you the question
- Treats a verified-address claim as proof the relying party checked something.
- Matches an assertion to an existing account on the address alone.
- Thinks a valid signature on the assertion settles which account it joins.
- Links on a name or partial address match when the address misses.
- Believes the interactive link is really just a second factor.
- Runs the link from the inbound flow with no proof of the existing account.