Someone signs up with a password in March. In June their company adopts single sign-on and they arrive again through their identity provider. Your system sees a new subject from a new issuer and, unless you decided otherwise, creates a second account. The person now has two, one of which contains all their work.
The fix is account linking, and it looks like a small feature. It is not. It is the point where every assumption you made about what identifies a person is tested at once, and it is one of the few operations in a product that is genuinely difficult to reverse.
The email address is not the identity
The instinct is to link on email, because email looks unique and every provider sends one. It is neither stable nor authoritative. It changes when people marry, when companies rebrand, when a domain migrates after an acquisition. It can be re-assigned to a new employee after a leaver's mailbox is recycled. And some providers will happily assert an address the account holder never proved they control.
The durable identity is the pair of issuer and subject: which authority is speaking, and which stable identifier it uses for this person. Store that pair per credential, allow a user record to own several of them, and treat email as an attribute hanging off the user rather than as the primary key. Making that change after a year of production data is a migration; making it on day one is a schema decision that costs nothing.
Email is a mailbox someone rents. Identity is the pair of issuer and subject, and only one of those two things is stable.
Linking on an unverified address is a takeover
Consider the sequence. An attacker registers with a password, using the address of somebody who has not signed up yet, and your system never confirms it. Later the real person arrives through a provider that asserts the same address. If your linking rule is match on email, you have just attached the legitimate account to a credential the attacker controls, and they have full access without ever seeing a password.
The rule that closes this is short: automatic linking requires both sides to be verified. Your local account must have confirmed the address, and the incoming assertion must come from a provider you trust to have verified it — which for many providers means checking an explicit verification claim rather than assuming. When either half is missing, do not link automatically. Ask the person to prove control of the existing account first.
- Link automatically only when both addresses are verified, and record why you believed each
- Treat a provider's email claim as unverified unless it says otherwise explicitly
- Require a fresh authentication on the existing account before attaching a new credential
- Never link on a name, a phone number without verification, or a fuzzy match of anything
- Log every link and unlink as an audit event with both identifiers named
Merging two accounts that both hold data
Linking credentials is the easy half. If both accounts have been used, you also have two sets of data, and merging them is application-specific work that no identity layer can do for you. Ownership of documents, membership in organisations, billing relationships, notification preferences, and every foreign key pointing at the account you are about to retire.
The honest approach is to decide which records merge, which are chosen between, and which simply cannot be reconciled, then write that decision down before building anything. Keep the retired account as a tombstone rather than deleting it, so historical audit entries still resolve to something meaningful. And do the merge in a background job that can be re-run, because it will fail halfway through at least once during development and you want to find out then.
- Enumerate every table referencing the account before touching the first one
- Pick the surviving identifier deliberately; rewriting references is what makes it irreversible
- Keep a tombstone so old audit records and shared links still resolve
- Run it as a resumable job, not inside a request handler with a timeout
Unlinking, and the door you can lock behind yourself
Whatever links must also unlink, because people leave companies, revoke third-party access and change providers. The obvious hazard is that a person removes their last remaining credential and can no longer sign in at all — a support burden created by a button you built.
Refuse to remove the final credential unless another is established first, and say so in the interface rather than in an error after the click. There is a second, quieter case: an organisation that requires single sign-on should not allow members to unlink from it, because that would convert an enforced policy into a personal preference. Whether a credential can be detached is a question about the organisation's policy, not only about the user's preferences.
Everything here, already built
Sign-in, enterprise SSO, directory provisioning, roles and an audit trail behind one API. Start with the quickstart and have a working sign-in this afternoon.