Quickstart
Add login to your app in about ten minutes. This walkthrough uses the hosted Login with Foir flow — Foir renders the login screen, your app gets customer tokens back — and never defines a content model. At the end you have real customers who can sign in.
You’ll need a Foir project and an API key. If you don’t have a project yet, create one in the admin app; you do not need to add any models.
Note: This is the fast path. For the full reference — admin lane, OIDC discovery, verifying tokens yourself — see Login with Foir.
1. Register your app
A relying party is your app as Foir sees it. Declare it in foir.config.ts and push it. Relying parties are project-scoped.
// foir.config.ts
import { defineConfig, defineRelyingParty } from '@foir/cli/configs';
export default defineConfig({
name: 'My App',
relyingParties: [
defineRelyingParty({
clientId: 'my-app',
name: 'My App',
redirectUris: ['https://myapp.com/auth/callback'],
allowedScopes: ['profile'],
}),
],
});foir push --tenant <tenant> --project <project>foir push creates the relying party on the first push and updates it on later ones. The redirectUris are an exact-match allow list — add your local URL (for example http://localhost:3000/auth/callback) while developing. See defineRelyingParty for every field.
2. Grant your API key the login scope
The login mutations are gated behind the auth:login_with_foir:customer scope. Add it to the key your app uses:
defineApiKey({
name: 'My App (public)',
keyType: 'public',
envVar: 'PLATFORM_API_KEY',
scopes: ['auth:login_with_foir:customer'],
});Push again to apply it.
3. Send users to hosted login
When a user clicks “Sign in”, call customerLoginWithFoir from your server. It returns a redirectUrl on auth.foir.dev — redirect the browser there.
mutation BeginLogin($input: LoginWithFoirStartInput!) {
customerLoginWithFoir(input: $input) {
redirectUrl
}
}Pass your clientId, the redirectUri you registered, and a returnTo path. Foir handles the password / OTP / OAuth screen from here.
4. Handle the callback
Foir sends the user back to your redirectUri with code and state query parameters. Exchange them for tokens:
mutation CompleteLogin($input: LoginWithFoirCallbackInput!) {
customerLoginWithFoirCallback(input: $input) {
token
refreshToken
customerId
email
returnTo
}
}You now have a signed-in customer. Store the tokens however your app manages sessions — send them to the browser as bearer tokens, or swap them for an HttpOnly cookie with a first-party session (BFF).
5. Call the API as the customer
Send the access token as a bearer token alongside your API key:
curl https://api.foir.dev/graphql \
-H "Authorization: Bearer <token>" \
-H "x-api-key: <your-public-key>"The 15-minute access token is an EdDSA JWT. To verify it on your backend, fetch the public keys from https://api.foir.dev/.well-known/jwks.json (issuer foir-customer) — never ship a verifier to the browser. When it expires, exchange the refresh token via customerRefreshToken. See Customer Authentication for the token model.
What’s next
- How auth is billed — you’ll start accruing monthly active users as people sign in.
- Webhooks — get
customer.createdevents delivered to your backend. - Custom login domain — serve the hosted login from
login.your-domain.cominstead ofauth.foir.dev, viacustomDomainson the relying party. See Config reference. - No backend? A verified login domain also lets a pure SPA hold an
HttpOnlysession with no tokens in JavaScript — see SPA (no backend). - Build your own login UI instead? The Token API exposes
customerLogin,customerRegister, and OTP directly. - On React or Next.js? Skip the wiring — React & Next.js gives you a provider, hooks, and drop-in components.