Skip to Content

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:

FieldLooks likeGoes in
client_id030cf3e0-147b-4a4d-86de-8a8b0bdc7617Sign-in: the /authorize URL, your SDK configuration and /oauth/token
idclient_6a1f0c3e9b7d4e2a8c5f1b03The 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 typeWhere the code runsSecret?Flow
Single Page AppThe browser (React, Vue, Angular)❌Authorization Code + PKCE
Native / MobileThe user’s device (iOS, Android, React Native)❌Authorization Code + PKCE
Regular Web AppYour server (Express, Next.js server components)✅Authorization Code
Machine to MachineA backend service or worker, no human involved✅Client Credentials
Device / TV / CLIAn 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. The redirect_uri must match an entry exactly, including the scheme and the path, so http://localhost:3000 does not match http://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 on localhost).
  • 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

SettingEffect
refresh_token.token_lifetimeLifetime of issued refresh tokens, in seconds. Default 2592000 (30 days).
Access token lifetimeNot a client setting — it comes from the API the token is issued for (token_lifetime), defaulting to 24 hours.

refresh_token.expiration_mode and refresh_token.infinite_token_lifetime exist on the client object for Auth0 compatibility, but are not enforced today — only token_lifetime governs 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_methodHow the client authenticatesFor
client_secret_basicHTTP Basic header with client_id:client_secret (the default)Confidential clients
client_secret_postclient_id + client_secret in the form bodyConfidential clients
noneNo secret — the PKCE code_verifier is the proofPublic 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_uris is the only required field. Everything else defaults per spec: grant_types to ["authorization_code"], response_types to ["code"], token_endpoint_auth_method to client_secret_basic, application_type to web.
  • Unsupported metadata is rejected, not silently accepted. Requesting a grant type Faable doesn’t implement returns 400. Supported grants are authorization_code, refresh_token, client_credentials, urn:ietf:params:oauth:grant-type:device_code, urn:ietf:params:oauth:grant-type:token-exchange and Auth0’s passwordless OTP grant.
  • client_secret_expires_at is 0 — issued secrets don’t expire.
  • Public clients register with token_endpoint_auth_method: "none" and get no client_secret back; 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_uri and 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_origins is 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.register entries 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