Authentication
End-user auth with anonymous sessions, Apple / Google social login, email-password, and account linking — all backed by Amba-native JWTs.
Amba provides a complete end-user auth system for mobile apps. Every user starts with an anonymous identity stored client-side; they can later sign in with Apple, Google, or email / password, and Amba preserves their anonymous history through account linking.
Credentials live in your project's database (physically isolated from every other Amba project) and passwords are hashed with bcrypt — no DIY crypto, no third-party auth vendor to get locked into.
How it works
- On first launch,
init()generates a localanonymous_idand persists it to storage. - When the user signs in (Apple, Google, or email), the SDK exchanges a credential for a session token (short-lived JWT) + refresh token (90 days). Both are persisted.
- The SDK attaches
Authorization: Bearer <session_token>to every user-scoped request. - When the session expires, the SDK rotates via
POST /client/auth/refresh(rotating both tokens and revoking the old refresh token's server-side record). logout()revokes the refresh token server-side and clears local state.
JWT claims
Session tokens carry:
| Claim | Meaning |
|---|---|
sub | the user's id |
pid | projectId — defense in depth: the API rejects a token that doesn't match the API key's project |
anon | the original anonymous_id |
Refresh tokens additionally carry a sid (session id) that maps to a server-side session record. Rotation revokes the old session and mints a new one, so a replayed refresh token is detected.
Display names
Every user has a non-null display_name. If the caller doesn't supply one at signup, Amba generates a friendly nature-themed name like "OakHiker", "DustOwl", or "RiverWalker" from a curated wordlist.
This applies to every signup path that creates a new user:
| Path | Behaviour |
|---|---|
POST /client/auth/anonymous | Always generates — there's no name to take from the caller. |
POST /client/auth/social | Uses the provider's name claim if present (Google), generates if absent (Apple withholds name on subsequent sign-ins). |
POST /client/auth/email/signup | Uses body.display_name if supplied, generates otherwise. |
POST /client/auth/magic-link/verify | Always generates on the new-user branch — magic-link has no name field. |
The wordlist skews friendly and approachable so leaderboards, friends lists, and "Sam (you)"-style social UI work on every project without domain tuning. Display names aren't unique — expect duplicates as a project grows past a few dozen users. Users can change theirs at any time (see below).
Overriding the generated name
The user can change their display name at any time via PATCH /client/users/me:
For email signup, you can supply display_name directly in the request body so the user is never briefly labelled OakHiker in the UI:
SDK usage
The Amba auth module signatures (same shape in @layers/amba-web, @layers/amba-node, @layers/amba-expo, and @layers/amba-react-native):
Anonymous identity (automatic)
Email sign-up and sign-in
Passwords are hashed server-side with bcrypt at cost factor 10. The plaintext password is never stored and never returned to the client.
Email magic-link
Passwordless email sign-in. The user enters their email, gets a one-tap link in their inbox, and is signed in (or signed up) on click — no password to remember, nothing to mint a user-side via the admin API.
The flow is two endpoints:
Tokens are 32 bytes (base64url, ~256 bits of entropy), expire in 15 minutes, and are single-use — a verify call marks used_at atomically so a replayed link returns 401 INVALID_TOKEN. Only the SHA-256 of the token lives in the DB; the raw token only ever exists in the user's mailbox.
If the email doesn't yet map to a user, verify creates one (with the email pre-verified). If it does — for example because the user previously did /email/signup — verify links the same user, so history is preserved across passwordless and password-based sign-ins.
The /request endpoint is rate-limited per email (5/hour, 20/day) and per IP (10/min, 200/day). Re-requesting because the first email got lost is fine; spamming someone's inbox isn't.
The redirect origin is taken from the request's Origin header, with MAGIC_LINK_REDIRECT_BASE_URL as an explicit override and https://app.amba.dev as the final fallback. Amba sends the magic-link email for you — there's no email provider to configure.
Sign in with Apple
The server verifies the token's signature and issuer against Apple's JWKS before trusting any claims.
Sign in with Google
Same JWKS verification on the server.
Account linking
Preserve a user's anonymous history when they later add a social login:
Email linking goes through the verified signUpWithEmail / signInWithEmail flow, not linkAccount.
Session management
Session shape:
Token refresh
The SDK persists refreshToken automatically. Apps typically never call refresh manually — when a session token expires, the SDK rotates both tokens transparently.
If you need to force-rotate (e.g. after a permissions change on the server), call Amba.auth.refresh():
Current user
Amba.auth.me() fetches /client/users/me using the current session token and returns the AppUser record:
Error handling with AmbaApiError
Every Amba SDK call rejects with an AmbaApiError on failure. Narrow with instanceof and branch on the stable .code field rather than parsing .message:
The same AmbaApiError class is exported from @layers/amba-web, @layers/amba-react-native, and @layers/amba-expo. Code that handles errors uniformly across the web and Expo halves of an app can import from either and the instanceof check matches.
Common AmbaApiError.code values on auth
| Code | When |
|---|---|
INVALID_CREDENTIALS | Wrong email/password on signInWithEmail. |
USER_EXISTS | Email already registered on signUpWithEmail. |
INVALID_EMAIL | Malformed email. |
WEAK_PASSWORD | Password doesn't meet policy. |
RATE_LIMITED | Too many attempts from this IP / user. |
INVALID_TOKEN | Apple/Google token failed signature verification. |
Switch on err.code rather than err.message — messages are human-readable and may change.
Expo one-liners
@layers/amba-expo wraps the native sign-in flows so you don't have to touch Apple / Google SDKs directly. The Expo wrappers call Amba.auth.signInWithSocial(provider, idToken) under the hood once the native prompt returns:
For email auth, call Amba.auth.signUpWithEmail(...) and Amba.auth.signInWithEmail(...) directly — they're available on every Amba SDK.
Routes reference
| Method | Path | Description |
|---|---|---|
POST | /client/auth/anonymous | Create a new anonymous user + session |
POST | /client/auth/social | Exchange an Apple or Google identity token for an Amba session |
POST | /client/auth/email/signup | Email signup (bcrypt-hashed password) |
POST | /client/auth/email/login | Email sign-in (verifies bcrypt hash) |
POST | /client/auth/magic-link/request | Send a one-tap sign-in link to the given email |
POST | /client/auth/magic-link/verify | Exchange a magic-link token for a session |
POST | /client/auth/link | Link the current anonymous session to a social provider |
POST | /client/auth/refresh | Rotate session + refresh tokens |
POST | /client/auth/logout | Sign out — revoke the refresh token |
All client auth routes require X-Api-Key. /link, /refresh, and /logout additionally require the current session / refresh token in the body.