federatedTokenProvider. The same pattern works in any browser or mobile app on @roadie/sdk.
No permanent secret ever ships in the app. The device sends its existing ID token plus your
publishable key (
rd_pk_…), which can do nothing but mint. See why this is safe in
Client tokens.How it works
Prerequisites
- An existing IdP that issues ID tokens (JWTs) — this tutorial uses Firebase Auth.
- A Roadie project with a validated provider credential (OpenAI or Anthropic — BYOK).
- A publishable key (
rd_pk_…) for the environment you will call.
1. Configure federated auth on the project
In the dashboard, configure the project’s federated auth with your IdP’s details:- Issuer — the exact
issyour IdP stamps (for Firebase,https://securetoken.google.com/<project-id>). - JWKS URL — where the gateway fetches the IdP’s public verification keys.
- Audience — the
audyour tokens carry (for Firebase, your Firebase project id). - Claim mapping — which claim is the end-user id (e.g.
suboruser_id), and optionally which claim carries the plan.
2. Install the SDK
3. Wire federatedTokenProvider
Hand the SDK a tokenProvider that performs the exchange. You give it your publishable key, the
gateway base URL, and a function that returns the user’s current ID token; the SDK caches the
minted token and refreshes it at ~80% of its TTL, single-flighting concurrent refreshes.
roadie.ts
/v1/client-tokens with the
publishable key as the bearer and { identity_token } in the body; the gateway distinguishes Path B
from the secret-key Path A by the credential kind.
4. Call Roadie as the signed-in user
From here the API is identical to the server tutorials — but every call is authenticated as the signed-in end user, with that user’s limits and quotas, and no secret key in the app:chat.ts
user here — the end-user id is carried in the token, derived from your IdP’s
claim mapping. (If you do pass user, it must match the token or be omitted.)
5. The plan is decided at the server
Whatever plan your IdP asserts in the token, the mint uses the effective entitlement Roadie holds for that end user when one exists — the developer-authoritative tier always wins, and a client can never raise its own tier by asserting a higher plan. The asserted claim is only a fallback when no entitlement is on record. So you can still gate Pro features server-side: grant the entitlement with your secret key (for example from a purchase webhook), exactly as in Free → Pro with StoreKit — the difference is only where the end-user id comes from.6. Verify
Sign in, send a message, and open the dashboard’s request logs. The request is attributed to the end-user id your claim mapping selected (e.g. the Firebasesub), and per-user limits apply. If the
exchange fails:
400federated_auth_not_configured— finish step 1 for this project.401invalid_identity_token— the ID token failed issuer/audience/signature verification; re-check the issuer,aud, and JWKS URL.
Why this is safe
- No secret ships. The publishable key can only mint — and a leaked one mints nothing without a valid ID token from your IdP.
- Short blast radius. Client tokens live at most an hour and are scoped to one end user’s limits.
- Your DB stays yours. Roadie verifies tokens against your JWKS and never sees your user records.
- Cut off a session by blocking the end user — the gateway’s denylist rejects that user within a second, and the short TTL does the rest. There is no per-token revocation to manage.
What you built
Your app calls Roadie directly as each signed-in user — chat, embeddings, and images scoped to that user’s own limits — with no backend of your own and no secret in the binary. Your existing Firebase (or Auth0 / OIDC) login is the sole root of trust; Roadie exchanges its ID token for a short-lived client token and enforces per-user limits from there.Next steps
Per-user limits
Give each federated user their own rate limits and quotas.
Free → Pro with StoreKit
Gate premium features by granting a server-side entitlement.
Client tokens
Minting paths, scopes, TTLs, and the safety model.
App Attest device sign-in
No IdP at all? Authenticate the device itself instead.