OIDC endpoint reference

Every realm exposes a standard OpenID Connect surface. Paths below are relative to the realm's issuer URL — https://{issuer_id}.semauth.app (or the realm's custom domain). Start from the discovery document; most OIDC client libraries configure themselves from it automatically.

Endpoint summary

PathMethodPurpose
/.well-known/openid-configurationGETDiscovery metadata.
/jwks.jsonGETThe realm's active public signing keys (JWKS).
/authorizeGETAuthorization endpoint (code flow).
/tokenPOSTToken endpoint (3 grant types).
/userinfoGETClaims for the authenticated user.
/revokePOSTToken revocation (RFC 7009).
/introspectPOSTToken introspection (RFC 7662).
/logoutGET / POSTRP-initiated logout (GET) / first-party logout (POST).

Discovery

GET /.well-known/openid-configuration returns the metadata your library needs. Notable advertised values:

response_types_supported["code"]
grant_types_supported["authorization_code", "refresh_token", "client_credentials"]
code_challenge_methods_supported["S256"] (PKCE is mandatory)
id_token_signing_alg_values_supported["RS256"]
token_endpoint_auth_methods_supported["none", "client_secret_basic", "client_secret_post", "private_key_jwt"]
scopes_supported["openid", "email", "profile", "offline_access", "management:full"]
claims_supportedsub, iss, aud, exp, iat, email, email_verified, auth_time, nonce, amr, acr, at_hash, org_id, scope

Authorization — GET /authorize

ParameterNotes
response_typeRequired. Only code is supported.
client_idRequired. Must be enabled and registered for authorization_code.
redirect_uriRequired. Exact match (loopback relaxation for native/CLI clients).
code_challengeRequired — PKCE is mandatory.
code_challenge_methodOptional, defaults to S256 (the only accepted value).
scopeOptional, defaults to openid. Each scope must be in the client's allowed scopes. offline_access requests a refresh token.
state, nonceOptional. Round-tripped; nonce is echoed into the ID token.
promptnone (no UI) or login (force re-auth).
max_ageOptional. Forces re-auth if the session is older.
login_hintOptional. Pre-fills the email field.
audienceOptional resource-server audience. Must be in the client's allowed audiences and a visible API. When present, the access token is a JWT (see token formats).

On success SemAuth redirects to redirect_uri with ?code=…&state=…. The code is single-use and valid for 10 minutes. With prompt=none and no satisfactory session you get an error redirect (login_required, or unmet_authentication_requirements if the session is below the realm's assurance floor).

Token — POST /token

Form-encoded. Client authentication is one of none (+PKCE), client_secret_basic, client_secret_post, or private_key_jwt — see client authentication methods.

grant_type = authorization_code

Send code, redirect_uri, and the PKCE code_verifier. Returns:

{
  "access_token": "sa_at_…",     // opaque; or a JWT if audience was requested
  "token_type": "Bearer",
  "expires_in": 3600,
  "id_token": "<JWT, RS256>",
  "scope": "openid email offline_access",
  "refresh_token": "sa_rt_…"     // only if offline_access was granted
}

Replaying an already-used code revokes the whole grant and its descendant tokens (theft defense).

grant_type = refresh_token

Send refresh_token. SemAuth always rotates the refresh token (issues a new sa_rt_… and invalidates the old one). If a previously-used token is presented again, reuse detection revokes the entire family. The original audience, nonce, and auth_time are preserved across rotations. Refresh tokens live 30 days.

grant_type = client_credentials

Machine-to-machine; no user. Requires a confidential client and an audience (which must be in the client's allowed audiences and a visible API). Always returns a JWT access token; no id_token, no refresh token. See the Admin API page for the canonical use.

Token formats

Opaque sa_at_…JWT access token
Issued whenCode / refresh flow with no audience.Any flow with an audience; always for client_credentials.
Verified byDB lookup at /userinfo / /introspect.Signature check against /jwks.json (stateless).
RevocableYes (immediately).No — valid until exp.
Lifetime1 hour1 hour

JWT access-token claims: iss, sub (user UUID, or client_id for client_credentials), aud (the audience), iat/exp (1h apart), jti, scope, and org_id — present only when the minting realm is an organization's admin realm.

ID token

RS256, signed by the realm's active key (with kid in the header), 1-hour lifetime. Claims: iss, sub (user UUID), aud (client_id), iat/exp, auth_time, email, email_verified, amr, acr, at_hash, and nonce when supplied.

UserInfo — GET /userinfo

Present an opaque access token as Authorization: Bearer sa_at_…. Returns sub, plus email and email_verified when the email scope was granted. (JWT access tokens are stateless and are not consumable here — read their claims directly instead.)

Revocation & introspection

Logout

See Sessions & logout for the end-to-end behavior.

A complete code-flow example

# 1. Send the user to authorize (your library builds this URL + PKCE):
GET https://{issuer_id}.semauth.app/authorize
      ?response_type=code
      &client_id=YOUR_CLIENT_ID
      &redirect_uri=https://app.acme.com/callback
      &scope=openid%20email%20offline_access
      &code_challenge=...&code_challenge_method=S256
      &state=...&nonce=...

# 2. Exchange the returned code:
curl -X POST https://{issuer_id}.semauth.app/token \
  -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
  -d grant_type=authorization_code \
  -d code=THE_CODE \
  -d redirect_uri=https://app.acme.com/callback \
  -d code_verifier=THE_PKCE_VERIFIER