...
Back

One Account, Three Platforms, Six Ways to Sign In

Unifying identity across web, iOS and Android is not an auth problem. It is a question of what counts as the same person, and you have to answer it before you write any code.

One Account, Three Platforms, Six Ways to Sign In

One Account, Three Platforms, Six Ways to Sign In 🔑

We shipped identity unification across web, iOS and Android — one account, same data everywhere, regardless of which platform you signed up on or which method you used. It sounds like plumbing. It is mostly a definitional problem, and the plumbing only gets easy after the definition is settled.


The question that has to be answered first

What makes two sign-ins the same person?

Every implementation detail follows from this, and getting it wrong is expensive in both directions: merge too eagerly and you hand one user another's data; merge too reluctantly and a real person ends up with three accounts and a support ticket about missing history.

The candidates, and why each is imperfect:

Email. The obvious answer and a dangerous one alone. Some identity providers let users hide their real address behind a relay, so the same human presents different addresses on different platforms. Others do not verify the address at all — and an unverified email as a merge key means anyone who types your address inherits your account.

Phone. Verified by construction when you send a code, which is its strength. But numbers get recycled by carriers, so a phone number identifies a subscription, not a person, over long enough time.

Provider subject identifier. Stable and trustworthy within one provider. Useless across providers — the same person signing in with two different social accounts is two subjects, and nothing in either token says they are related.

The workable model is: a user is a durable internal identifier, and every sign-in method is a credential linked to it. Never treat any external identifier as the user; treat it as a key that points at one.


The rules we settled on

Only merge on verified, non-relayed identifiers. Verified email or verified phone. A relayed address links to the account it was issued for and nothing else. An unverified address links to nothing at all.

Linking requires proof of both sides. Adding a second sign-in method to an existing account means being signed in and proving the new credential. Accepting a new method because it happens to carry a matching email is account takeover with extra steps.

Never auto-merge two accounts that both have data. This is irreversible in practice. If a user genuinely has two accounts, that is a support flow with explicit confirmation about what happens to each side's history — not something a login handler decides at three in the morning.

One canonical user identifier, everywhere. Every platform, every table, every analytics event. The moment a device stores its own notion of "who this is" that can disagree with the server, you have a class of bug that reproduces only for the user reporting it.


Where the platform differences actually bite

Token lifetimes differ by platform, and refresh is not free. A mobile app that force-refreshes a token before every operation will hang for however long that refresh takes on a bad network — which, for an identity endpoint that is slow or blocked in some regions, can be minutes. Cache the credential, refresh in the background, and put a ceiling on any refresh that sits in front of a user action.

Sign-in with a native provider requires per-platform configuration. The same provider needs different client identifiers and different return URLs for web versus native, and a provider enabled for one platform but misconfigured for another produces failures that look like user error. The audit is boring and it is the whole job: for every provider, for every platform, is it registered, enabled, and pointed at the right return URL?

Account deletion has to be reachable on every platform that offers sign-up. Stores require this, and beyond compliance, an account users cannot delete from the device they created it on is a support burden forever.


What we would do differently

Model identity as user plus linked credentials from the first commit, even when there is exactly one sign-in method and the split looks like over-engineering. Retrofitting it later means a migration over live accounts, where every ambiguous row is a real person whose data you are about to move.

And write down the merge rule — in the code, as a comment, in plain language. Ours amounts to one sentence: link on verified email or verified phone; never merge two accounts that both hold data. Every incident in this area comes from someone reasoning about the rule from scratch and reaching a slightly different conclusion than the person before them.