Skip to content

OIDC endpoints ​

Reference for the OpenID Connect and OAuth 2 endpoints your organization serves, the tokens they issue, and the rate limits that apply. Most libraries only need the issuer and read the rest from discovery.

The examples use https://acme.fobauth.com. The console lists your organization's URLs under Endpoints on Settings > General. If you serve sign-in on a custom domain, every endpoint works there too, and the issuer matches the URL the app uses. See Custom domains.

For SAML endpoints, see SAML apps. For the management API and SCIM, see Management API and SCIM.

Endpoints ​

EndpointMethodsPath
DiscoveryGET/.well-known/openid-configuration
Authorization server metadata (RFC 8414)GET/.well-known/oauth-authorization-server
AuthorizationGET, POST/oauth/authorize
TokenPOST/oauth/token
Device authorization (RFC 8628)POST/oauth/device_authorization
UserinfoGET, POST/oauth/userinfo
JWKSGET/oauth/jwks
Introspection (RFC 7662)POST/oauth/introspect
Revocation (RFC 7009)POST/oauth/revoke
Sign-outGET, POST/oauth/end-session

Browser apps can call discovery, JWKS, token, device authorization, userinfo, introspection and revocation directly: they allow cross-origin requests from any origin and don't use cookies.

Discovery document values

Both discovery paths return the same document, with the URL of each endpoint above and these values:

FieldValue
response_types_supportedcode
response_modes_supportedquery
grant_types_supportedauthorization_code, refresh_token, urn:ietf:params:oauth:grant-type:device_code
code_challenge_methods_supportedS256
token_endpoint_auth_methods_supported, revocation_endpoint_auth_methods_supportedclient_secret_basic, client_secret_post, none
introspection_endpoint_auth_methods_supportedclient_secret_basic, client_secret_post
id_token_signing_alg_values_supportedRS256
subject_types_supportedpublic
scopes_supportedopenid, profile, email, groups, offline_access
prompt_values_supportednone, login
claims_supportedsub, iss, aud, exp, iat, auth_time, nonce, azp, at_hash, sid, amr, act, email, email_verified, name, given_name, family_name, picture, updated_at, groups
authorization_response_iss_parameter_supportedtrue
request_parameter_supported, request_uri_parameter_supported, claims_parameter_supportedfalse

Tokens ​

TokenFormatLifetime
Authorization codeOpaque, single use2 minutes
Access tokenJWT (RFC 9068), typ is at+jwt15 minutes
ID tokenJWT1 hour
Refresh tokenOpaque, single use30 days from the original sign-in

Tokens are signed with RS256 by a key in your organization's JWKS. An access token's aud is the app's own client ID. Fob doesn't support the resource or audience parameters (RFC 8707) and ignores them.

Refresh tokens ​

Ask for the offline_access scope to get a refresh token. Each refresh returns a new refresh token, and the old one stops working.

Refresh from one place at a time

Using a refresh token twice revokes every refresh token from that sign-in, so the user has to sign in again. This includes two requests sending the same token at the same moment.

Refresh tokens from one sign-in stop working 30 days after it, however often you refresh. They're also revoked when the user signs out, their session is ended, or they're disabled.

Fob never issues refresh tokens during an impersonation.

Scopes and claims ​

Apps get claims based on the scopes they ask for, in the ID token and from userinfo. Fob ignores scopes it doesn't recognise.

ScopeClaims
openidsub, the user's ID, which never changes. Required.
emailemail and email_verified. The app's Email address sent setting picks which address.
profilename, given_name, family_name, picture, updated_at, and custom attributes with Send to apps on, by key
groupsgroups: the names of the user's groups. The built-in Everyone group isn't included.
offline_accessA refresh token
json
{
  "iss": "https://acme.fobauth.com",
  "sub": "0193a4f2-7c1e-7b3a-9f0e-2d4c5b6a7e8f",
  "email": "ada@acme.com",
  "name": "Ada Lovelace",
  "employee_id": "E-1024",
  "groups": ["Platform"]
}

Group changes show up in the next token Fob issues, including tokens from a refresh.

To check a user's groups in a Node app with jose:

js
import { createRemoteJWKSet, jwtVerify } from "jose";

const issuer = "https://acme.fobauth.com";
const jwks = createRemoteJWKSet(new URL(`${issuer}/oauth/jwks`));

export async function requireGroup(idToken, group) {
  const { payload } = await jwtVerify(idToken, jwks, { issuer, audience: "fob_8c1d2e3f4a5b6c7d8e9f0a1b" });
  if (!payload.groups?.includes(group)) throw new Error(`Not in ${group}`);
  return payload;
}

How the user signed in: amr ​

ID tokens carry amr (RFC 8176). To require MFA in your app, check that it contains "mfa".

Sign-inamr
Password["pwd"]
Emailed reset or confirmation link["email"]
Passkey alone["hwk", "user", "mfa"]
Password and authenticator app["pwd", "otp", "mfa"]
Password and passkey["pwd", "hwk", "mfa"]
Password and recovery code["pwd", "kba", "mfa"]
Enterprise SSOThe identity provider's amr, if it sends one and Fob didn't ask for its own second factor

ID tokens also carry sid, the user's Fob session. Access tokens carry sid while that session lasts.

Impersonation: act ​

When an admin impersonates a user, the ID token and access token carry act (RFC 8693), naming the admin. The ID token has no amr, because the user didn't sign in.

json
{
  "sub": "0193a4f2-7c1e-7b3a-9f0e-2d4c5b6a7e8f",
  "act": {
    "sub": "0192ff10-3b4d-7e21-8c90-5a6b7c8d9e0f",
    "email": "grace@acme.com"
  }
}

Check for act to block actions a helper shouldn't take on someone's behalf, such as approving expenses:

js
if (payload.act) {
  return res.status(403).json({ error: "Not allowed while impersonating." });
}

Userinfo and introspection responses don't include act.

Authorization ​

/oauth/authorize starts a sign-in with the authorization code flow. Send parameters in the query string, or as a form with POST.

ParameterNotes
client_idRequired.
redirect_uriRequired. Must exactly match one of the app's redirect URLs. For desktop and mobile apps, http://127.0.0.1, http://[::1] and http://localhost URLs match on any port.
response_typeRequired. Must be code.
scopeSpace-separated. Up to 1,024 bytes.
stateReturned unchanged. Up to 2,048 bytes.
nonceCopied into the ID token. Up to 1,024 bytes.
code_challenge, code_challenge_methodPKCE. The method must be S256. Required unless Require PKCE is off for a web app.
response_modeOnly query.
promptnone returns an error instead of showing any page. login makes the user sign in again. none can't be combined with other values.
max_ageSeconds. A session older than this signs in again.

On success, Fob redirects to redirect_uri with code, state and iss. The code works once, within 2 minutes, and only while the user stays signed in. An unfinished sign-in expires after 30 minutes.

If the client ID is unknown, the redirect URL isn't registered, or a parameter is sent twice, Fob shows its own error page instead of redirecting. Other errors redirect with error, error_description, state and iss:

errorWhen
invalid_requestMissing or bad PKCE, an unsupported response_mode, a non-numeric max_age, a parameter that's too long, or prompt=none combined with other values
unsupported_response_typeresponse_type isn't code
request_not_supported, request_uri_not_supportedThe request used a request object
login_requiredprompt=none and the user isn't signed in
access_deniedprompt=none and the user isn't assigned, or a sign-on policy denies them
interaction_requiredprompt=none and the sign-on policy needs the user to sign in again

Token ​

/oauth/token takes a form-encoded POST.

Web apps authenticate with their client secret, using either HTTP Basic (client_secret_basic) or client_id and client_secret form fields (client_secret_post). Sending it both ways is refused. Browser, desktop and mobile apps send only client_id, and are refused if they send a secret.

grant_typeParameters
authorization_codecode, redirect_uri (the same one the sign-in used), and code_verifier if the sign-in sent a challenge
refresh_tokenrefresh_token
urn:ietf:params:oauth:grant-type:device_codedevice_code. See Device authorization.

The response has access_token, token_type (Bearer), expires_in, scope, id_token when the scope includes openid, and refresh_token when it includes offline_access. scope can be narrower than what was asked for.

A code is used up by the first attempt, even a failed one. Presenting a code again revokes the refresh tokens it produced.

errorStatusWhen
invalid_client401Unknown client ID, wrong secret, a secret sent by a public app, or a SAML app's ID
invalid_request400A missing parameter, a parameter or secret sent twice, or a body that isn't application/x-www-form-urlencoded
invalid_grant400The code or refresh token is wrong, expired, used or revoked; redirect_uri or code_verifier doesn't match; the session has ended; or the user is disabled, can no longer use the app, or has no address at the app's email domain
unauthorized_client400Device sign-in is off for the app
authorization_pending, slow_down, access_denied, expired_token400Device sign-in, while waiting for the user. See below.
unsupported_grant_type400Any other grant type
temporarily_unavailable429Too many requests. See Rate limits.

Device authorization ​

For command-line tools and devices without a good browser. The app shows a short code, the user enters it on another device, and the app polls for tokens. Turn on Allow the device authorization grant under Device sign-in on the app's General tab first.

  1. POST client_id and scope to /oauth/device_authorization. Web apps also send their secret. The response has device_code, user_code (like BCDF-GHJK), verification_uri (your organization's /device page), verification_uri_complete (the same with the code filled in), expires_in (600 seconds) and interval (5 seconds).
  2. Show the user user_code and verification_uri, or a QR code of verification_uri_complete.
  3. Every interval seconds, POST grant_type=urn:ietf:params:oauth:grant-type:device_code, device_code and client_id to /oauth/token, until it returns tokens.
error while pollingWhat to do
authorization_pendingKeep polling.
slow_downAdd 5 seconds to your polling interval.
access_deniedThe user declined, or signed out first. Stop.
expired_tokenThe code expired. Start again.

The user always signs in again after entering the code, even if they're already signed in. The app's assignments and sign-on policy apply. Each device code works once, and the tokens are the same as from a normal sign-in.

Userinfo ​

Call /oauth/userinfo with an access token in the Authorization: Bearer header. The response has sub and the claims the token's scopes allow.

StatuserrorWhen
401invalid_tokenThe token is missing, expired or unknown, its session has ended, or the user can no longer use the app
403insufficient_scopeThe token doesn't include openid

Introspection ​

/oauth/introspect tells a backend whether a token is still valid. Only web apps can call it, authenticating with their client secret. Send the token as the token form field.

An active access token returns active: true, token_type: "access_token", sub, client_id, scope, exp, iat, iss, aud and jti. An active refresh token returns active: true, token_type: "refresh_token", sub, client_id, scope, iat, exp and iss.

Everything else returns {"active": false}, including tokens issued to other apps and tokens for a user who has since been disabled, signed out or lost access to the app.

Revocation ​

/oauth/revoke revokes a refresh token and every other refresh token from the same sign-in. Send it as the token form field, authenticating as at the token endpoint. The response is 200 whether or not Fob knew the token.

Access tokens can't be revoked. They expire after 15 minutes, and introspection reports them inactive as soon as the user loses access.

Sign-out ​

/oauth/end-session signs the user out of Fob (RP-initiated logout). Send parameters in the query string, or as a form with POST.

ParameterNotes
id_token_hintAn ID token Fob issued. Expired tokens are accepted.
client_idIdentifies the app. Must match the ID token's audience if both are sent.
post_logout_redirect_uriMust be one of the app's Sign-out addresses. Needs client_id or id_token_hint.
stateAdded to the return URL.

Fob signs the user out straight away only if id_token_hint names them and their current session. Otherwise it asks them to confirm. Without post_logout_redirect_uri, users land on the sign-in page.

If the ID token can't be verified, client_id doesn't match it, or the return URL isn't registered, Fob shows its own error page.

Rate limits ​

Over a limit, Fob responds 429 with a Retry-After header giving the seconds to wait. OAuth endpoints return "error": "temporarily_unavailable", the management API returns rate_limited, and SCIM uses its own error format.

Per IP address ​

Counted per minute, per IP address. IPv6 addresses are counted per /64. Each row has its own budget.

RequestsLimit per minute
Management API (/v1) and SCIM (/scim/v2), together3,000
Token, introspection, revocation and device authorization, together600
Access API (/access/v1), used by fob and servers600
Authorization (/oauth/authorize)300
Enterprise SSO and SAML (/sso/…, /saml/…)120
Opening a terminal in the browser60
Signing in with a password or passkey30
Entering a second-factor code30
Opening an invitation, reset or confirmation link30
Confirming it's you, and account changes that need it30
Setting a new password when asked at sign-in30
Looking up a user's identity provider from their email30
Asking for a reset or confirmation email10
Entering a device code on /device10

Per app ​

Each app can make 3,000 requests a minute to the token, introspection, revocation and device authorization endpoints, from all addresses together.

Per account ​

These follow the user's account wherever the requests come from, counted over 15 minutes. They slow down guessing on top of lockout.

RequestsLimit per 15 minutes
Password sign-in attempts20
Second-factor and recovery codes10
Tries at confirming it's you10
Reset emails3
Confirmation emails3

Emails and organizations ​

WhatLimit
Invitation, reset and email-change emails to one address, from the console and the API together5 an hour
The same emails from one organization in total500 a day
Admin invitations from one organization5 an hour to one address, 500 a day in total
Organizations one admin can create10 a day

Users you create with a password through the management API don't count towards the email limits.