Clients
In Faable Auth, a Client represents an application that authenticates users or requests authorization to access APIs: a Single Page Application (React, Vue, Angular), a native mobile app, a server-side web application, or a backend machine-to-machine service.
Clients are what initiate the OAuth 2.0 and OpenID Connect flows. You manage them from the Faable Dashboard , or create them programmatically with Dynamic Client Registration.
Creating a Client
Registering a client generates two credentials:
- Client ID — a public, unique identifier. Safe to ship in client-side code (React, Expo); it tells Faable Auth which application is asking.
- Client Secret — a confidential string that proves the application’s identity. Never expose it in a SPA, a mobile binary, or anything a user can read. It belongs to backends only.
Two identifiers
Every client has two ids, and the dashboard shows both:
| Field | Looks like | Goes in |
|---|---|---|
client_id | 030cf3e0-147b-4a4d-86de-8a8b0bdc7617 | Sign-in: the /authorize URL, your SDK configuration and /oauth/token |
id | client_6a1f0c3e9b7d4e2a8c5f1b03 | The Management API: GET /client/:id |
The dashboard labels them Client ID and ID. Using one where the other belongs fails, and the error says which one to use.
Types of Clients
Your architecture dictates the configuration and the flow:
| Client type | Where the code runs | Secret? | Flow |
|---|---|---|---|
| Single Page App | The browser (React, Vue, Angular) | ❌ | Authorization Code + PKCE |
| Native / Mobile | The user’s device (iOS, Android, React Native) | ❌ | Authorization Code + PKCE |
| Regular Web App | Your server (Express, Next.js server components) | ✅ | Authorization Code |
| Machine to Machine | A backend service or worker, no human involved | ✅ | Client Credentials |
| Device / TV / CLI | An input-constrained device | ❌ | Device Code |
Faable Auth enforces PKCE with S256 — it advertises no other code challenge method — so public clients are covered whether or not a secret is present.
Client Configuration
Security boundaries
- Allowed Callback URLs (
callbacks) — the whitelist of URLs Faable Auth may redirect to after login (e.g.https://myapp.com/callback). Prevents open-redirect abuse. Theredirect_urimust match an entry exactly, including the scheme and the path, sohttp://localhost:3000does not matchhttp://localhost:3000/callback. Only the port of a loopback address may differ. Register each domain your app is served from, including the default one if people can reach it. - Allowed Logout URLs (
logout_urls) — where users may be sent after RP-Initiated Logout. - Login URL (
initiate_login_uri) — the page in your app that starts a login by redirecting to/authorize(e.g.https://myapp.com/login). A login link carries a one-time state that belongs to the browser that opened it, so people who bookmark it, or forward it to someone else, end up on a link that can’t sign them in. When that happens, the hosted login screen sends them to this URL to start over instead of showing a form that won’t work. Without it, they get a button to the home page of your registered callback origin.https://only (http://is accepted onlocalhost). - Allowed Web Origins (
web_origins) — origins (scheme://host[:port], no path — e.g.http://localhost:5173) permitted to make cross-origin calls to the token and passwordless endpoints. When the list is empty, any origin is allowed; fill it in to lock browser calls down to your own app. Your account’s auth domain is always allowed implicitly, so you only list your application origins. - Connections — which authentication methods (Google, passwordless, database…) this specific client may use. Set on each connection via its Enabled clients list.
Management API scopes
management_scopes is the list of Management API scopes (read:users, read:rolemembers, update:clients…) this client may obtain with Client Credentials for the audience faable:management:<account_id>. A token request gets the requested scopes that are on the list, or the whole list when it asks for none. Empty — the default for a new client — means the client cannot call the Management API (403 management_scopes_empty).
Set it on the client page (OAuth tab → Management API scopes), when creating the client (Management API access), or with management_scopes on POST /client and POST /client/:id (replaces the whole list). You can only add scopes your own token holds.
Token lifetimes
| Setting | Effect |
|---|---|
refresh_token.token_lifetime | Lifetime of issued refresh tokens, in seconds. Default 2592000 (30 days). |
| Access token lifetime | Not a client setting — it comes from the API the token is issued for (token_lifetime), defaulting to 24 hours. |
refresh_token.expiration_modeandrefresh_token.infinite_token_lifetimeexist on the client object for Auth0 compatibility, but are not enforced today — onlytoken_lifetimegoverns how long a refresh token lives. Don’t rely on them to build a never-expiring session.
Client authentication at the token endpoint
POST /oauth/token accepts three methods, as published in your tenant’s discovery document:
token_endpoint_auth_method | How the client authenticates | For |
|---|---|---|
client_secret_basic | HTTP Basic header with client_id:client_secret (the default) | Confidential clients |
client_secret_post | client_id + client_secret in the form body | Confidential clients |
none | No secret — the PKCE code_verifier is the proof | Public SPA and native clients |
Application metadata
Optional fields, mostly consumed by consent screens and by tooling: client_uri, logo_uri, tos_uri, policy_uri, contacts, application_type (web | native), software_id, software_version.
Front-Channel Logout
A client may register a frontchannel_logout_uri: Faable loads it in a hidden iframe when the user logs out of the OP, so your application can clear its own session. Set frontchannel_logout_session_required to true and Faable includes iss and sid on that call, letting you scope the cleanup to the exact session. See Logout. A client may also register a backchannel_logout_uri: when the session ends, Faable POSTs a signed logout_token there from its own servers — no browser involved — so your application’s server-side session ends too. See Back-channel logout.
Dynamic Client Registration
Clients can be created over HTTP instead of through the dashboard, per OIDC Dynamic Client Registration 1.0 / RFC 7591 . This is what lets a third-party tool onboard itself against your tenant — MCP clients, IDE integrations and OAuth debuggers commonly expect it. For MCP servers, see Add OAuth to your MCP server.
It is off by default — an open registration lets anyone create a client with their own redirect_uri. Turn it on per tenant with the Management API:
curl -X POST 'https://your-domain.auth.faable.link/account/<account_id>' \
-H 'Authorization: Bearer <management token>' \
-H 'content-type: application/json' \
-d '{ "dynamic_client_registration_enabled": true }'While it’s off, POST /oidc/register answers 403 and the discovery document doesn’t list it. Once on, the endpoint is published in your discovery document as registration_endpoint:
curl -X POST 'https://your-domain.auth.faable.link/oidc/register' \
-H 'content-type: application/json' \
-d '{
"client_name": "My Tool",
"redirect_uris": ["https://tool.example.com/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "client_secret_basic"
}'Response (201), per RFC 7591 §3.2.1 — this is the only time the secret is returned:
{
"client_id": "…",
"client_secret": "…",
"client_id_issued_at": 1751641200,
"client_secret_expires_at": 0,
"redirect_uris": ["https://tool.example.com/callback"],
"client_name": "My Tool",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "client_secret_basic",
"application_type": "web"
}Notes on behaviour:
redirect_urisis the only required field. Everything else defaults per spec:grant_typesto["authorization_code"],response_typesto["code"],token_endpoint_auth_methodtoclient_secret_basic,application_typetoweb.- Unsupported metadata is rejected, not silently accepted. Requesting a grant type Faable doesn’t implement returns
400. Supported grants areauthorization_code,refresh_token,client_credentials,urn:ietf:params:oauth:grant-type:device_code,urn:ietf:params:oauth:grant-type:token-exchangeand Auth0’s passwordless OTP grant. client_secret_expires_atis0— issued secrets don’t expire.- Public clients register with
token_endpoint_auth_method: "none"and get noclient_secretback; they must use PKCE. - Every dynamically registered client asks for consent. Before it gets a token the user sees its name, the host of its
redirect_uriand the permissions it requested — even with an active session — and PKCE is mandatory. Its refresh tokens are single-use. Users can disconnect it from their security page. Clients you create in the dashboard are not affected. - Registrations are rate-limited per IP address.
- The client is bound to the tenant resolved from the request host, so register against the domain you actually want it to live in.
web_originsis accepted as a non-standard extra, with the same rules as in the dashboard.- Registration is not itself authenticated. Anyone who can reach your auth domain can create a client in your tenant — a new client grants no access on its own (its Management API scopes are empty), but if that’s not a trade-off you want, keep an eye on
oauth.client.registerentries in your logs.
Next Steps
- Connections — the identity providers you attach to a client.
- Authorization Code Flow — the mechanics of the standard login flow.
- APIs — register the resource servers your clients request tokens for.
- Quickstarts — jump into working code, including FastAPI on the backend side.
Last updated on