How Okia login works, and the ideas you need before integrating.
The login model
Okia replaces passwords with two factors, in order of preference:
| Method | What the user sees | When |
|---|---|---|
| Passkey | Face ID / Touch ID / Android biometric prompt | Every login after enrollment. Target: < 5s, zero typing |
| WhatsApp code | 6-digit code delivered to their WhatsApp | First-time enrollment, and always available as fallback |
All login happens on Okia's hosted pages at auth.okia.io — via popup on desktop or full redirect on mobile (the SDK auto-selects; redirect is the documented baseline). Your site never sees a phone number unless the user consents to the phone scope. There is no cross-origin iframe mode.
First login (enrollment)
- User clicks your login button → hosted sheet opens.
- Enters phone number → receives a 6-digit code on WhatsApp → verifies.
- Offered a passkey ("Sign in with Face ID next time"). Skippable — an OTP-only account still works.
- Redirected back to your site with an authorization code; your backend exchanges it for tokens.
Returning login
The user taps the phone field and their passkey appears (WebAuthn conditional UI) — one biometric confirmation, no form. If they have an active Okia session under 24h old, it's a one-tap "Continue as +9•••42".
Fallback
The WhatsApp code path is always one tap away. A fallback login sets amr: ["otp"] in the ID token (see step-up below) and fires a user.otp_fallback webhook. Passkeys are never deleted by an OTP login.
Pairwise subject IDs
The same person has a different sub on every site. This is deliberate.
- A user enrolls once into a global vault keyed by phone number.
- Each site receives
sub = "pw_" + base64url(HMAC-SHA256(site.pairwiseSalt, endUser.id))— derived from a per-site random salt. - Two sites — even two sites in the same organization — cannot correlate their users, and Okia provides no API to resolve a sub across sites. Lookup is scoped to
/v1/sites/{siteId}/users/{sub}only.
Consequences for you:
| Do | Don't |
|---|---|
Store sub as the stable user key for your site |
Expect the same sub on your other sites |
Request the phone scope if you genuinely need the number (double opt-in, per-site consent) |
Try to join users across sites by phone or sub |
Note: end users have no email in Okia. There is no email scope or claim.
Sessions: yours vs Okia's
Two independent sessions exist after login:
| Session | Where | Lifetime | Controlled by |
|---|---|---|---|
| Your site's session | Your cookie/backend, minted after the token exchange | Whatever you choose | You |
| Okia SSO session | __Host-pw_sess cookie on auth.okia.io |
30 days, rolling | Okia |
The SSO session is what makes login on a second connected site instant. Logging out of your site does not end the SSO session; GET /logout on the auth domain does. After a global logout, sites validating JWTs locally may accept an already-issued access token for up to 15 minutes (the access-token lifetime) — that lapse is by design and documented, not a bug.
"Remember this device" on the hosted sheet is the 30-day SSO session — disclosed to the user, not a checkbox.
The amr claim and step-up
Every ID token carries amr (authentication methods reference):
amr |
Meaning |
|---|---|
["webauthn"] |
Passkey login — phishing-resistant, user-verified |
["otp"] |
WhatsApp-code login — possession of the phone only |
If an action on your site is sensitive (payments, changing account details), check amr. When it's ["otp"], send the user back through /authorize and require a passkey before proceeding. auth_time tells you how fresh the authentication is if you need recency, not just method.
Account safety rules worth knowing
- Passkeys are never wiped by an OTP login, and for 24h after an OTP fallback login no passkey can be deleted at all.
- If a phone number is recycled to a new owner (account inactive ≥ 180 days), the old account is quarantined — the new owner gets a fresh identity, and the old owner can recover via their passkey. Your site sees a new
sub, never a silently transferred one.