Authentication
AKIN Travel auth is built on Google Identity Platform (GIP) / Firebase. Members sign in through one of three flows; each ends with a Firebase ID token that you send as Authorization: Bearer <idToken> on every member-scoped request.
On React, <AkinProvider> runs this entire exchange for you. On any other stack you drive the same GraphQL mutations and finish with the Firebase Auth SDK (web, Android, and iOS all have one).
Partner API keys: publishable vs secret
Every request carries your partner API key in the x-partner-api-key header. Keys come in two classes — pick the right one for where the key will live:
| Key class | Where it lives | Capability |
|---|---|---|
| Publishable (browser-safe) | Shipped to the browser — React bundle, <script data-api-key> embed, page source | Read-only by construction. It can start auth flows and read loyalty state, but it can never carry or exercise a write scope. |
| Secret | Your server only — never in a browser | Full capability. Grant it the scopes your backend needs (member:write to enrol, loyalty:write to earn/burn points). |
The two are independent of environment: both exist as pk_test_* (sandbox) and pk_live_* (production) keys. You choose the class when you create the key in the dashboard (a publishable key is flagged browser-safe; a secret key is the default).
Why it matters. A publishable key is safe to expose precisely because it’s read-only — even if a page visitor reads it out of your bundle, they can’t write. The API enforces this on both ends: a publishable key cannot be created with a write scope, and a write operation attempted with a publishable key is rejected server-side. Never ship a secret key to the browser; if you have, rotate it in the dashboard.
Use a publishable key for the browser SDKs (React quickstart, embed widget) and a secret key for the server SDK and any server-to-server write.
Supported sign-in flows
| Flow | When to use | API mutation | SDK shortcut |
|---|---|---|---|
| Magic link | Lowest friction for new members — a one-time link by email. | requestMagicLink → verifyAuthToken | <MagicLinkForm> |
| Passkey | Best UX once a device is enrolled. WebAuthn under the hood. | passkeyAuthenticationOptions → passkeyAuthenticate | <LoginForm> (auto-detects) |
| Email + password | Optional fallback when your audience expects passwords. | loginMemberGIP | <LoginForm>, <SignupForm> |
All flows return the same member shape, and steps 2–3 below are identical regardless of which one you start with.
The exchange (any stack)
- Start a flow — e.g.
requestMagicLink(email, userType: "MEMBER", partnerId, redirectUrl). Authenticated with yourx-partner-api-key; no member token yet. - Verify —
verifyAuthToken(token)returns{ customToken, userId, isNewUser, memberData }. ThecustomTokenis a short-lived Firebase custom token. - Sign in to Firebase — pass
customTokentosignInWithCustomToken(Firebase web/Android/iOS SDK). Firebase issues an ID token and refreshes it automatically. - Call the API — attach
Authorization: Bearer <idToken>to each GraphQL request.
See the Quickstart for runnable React / Vue / Android / iOS / cURL versions.
Sessions
The React SDK keeps the ID token in memory and refreshes it on demand via setTokenProvider; Apollo picks it up through the createAkinClient link, so you rarely call setTokenProvider yourself. Sign-out clears the in-memory token and the GIP session:
const { signOut } = useAkinAuth();
await signOut();Off React, the Firebase Auth SDK owns the session: getIdToken() returns a fresh token (refreshing as needed) and signOut() clears it.
Custom-token exchange (advanced)
If your app already authenticates users and you only want AKIN Travel for loyalty state, exchange your own identifier for a short-lived token via the custom-token path (partner API key required) and continue from step 3 above. This is the same path the SDK uses internally — see the GraphQL reference.
Guards (React SDK)
Two render-prop guards control conditional UI. They’re for client-side display only — every mutation is also authorised server-side, on every stack.
import { RequireAuth, RequireGuest } from '@akin-travel/partner-sdk';
<RequireAuth fallback={<SignInButton />}>
{({ member }) => <ProfileChip member={member} />}
</RequireAuth>
<RequireGuest fallback={null}>
<SignUpBanner />
</RequireGuest>Errors
Auth error codes (AUTH_REQUIRED, INVALID_TOKEN, TOKEN_EXPIRED, ACCESS_DENIED) are listed in the Error codes reference. Surface the extensions.code to your error UI, never the message — copy may change.