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 🔑
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.