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
| Path | Method | Purpose |
|---|---|---|
/.well-known/openid-configuration | GET | Discovery metadata. |
/jwks.json | GET | The realm's active public signing keys (JWKS). |
/authorize | GET | Authorization endpoint (code flow). |
/token | POST | Token endpoint (3 grant types). |
/userinfo | GET | Claims for the authenticated user. |
/revoke | POST | Token revocation (RFC 7009). |
/introspect | POST | Token introspection (RFC 7662). |
/logout | GET / POST | RP-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_supported | sub, iss, aud, exp, iat, email, email_verified, auth_time, nonce, amr, acr, at_hash, org_id, scope |
Authorization — GET /authorize
| Parameter | Notes |
|---|---|
response_type | Required. Only code is supported. |
client_id | Required. Must be enabled and registered for authorization_code. |
redirect_uri | Required. Exact match (loopback relaxation for native/CLI clients). |
code_challenge | Required — PKCE is mandatory. |
code_challenge_method | Optional, defaults to S256 (the only accepted value). |
scope | Optional, defaults to openid. Each scope must be in the client's allowed scopes. offline_access requests a refresh token. |
state, nonce | Optional. Round-tripped; nonce is echoed into the ID token. |
prompt | none (no UI) or login (force re-auth). |
max_age | Optional. Forces re-auth if the session is older. |
login_hint | Optional. Pre-fills the email field. |
audience | Optional 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 when | Code / refresh flow with no audience. | Any flow with an audience; always for client_credentials. |
| Verified by | DB lookup at /userinfo / /introspect. | Signature check against /jwks.json (stateless). |
| Revocable | Yes (immediately). | No — valid until exp. |
| Lifetime | 1 hour | 1 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
POST /revoke(RFC 7009) — client-authenticated. Revokes an opaque access token, or a refresh token (which revokes its whole family). Always returns 200.POST /introspect(RFC 7662) — client-authenticated. For a live opaque token returnsactive:truewithscope,client_id,sub,exp,iss; otherwise{"active":false}. Only opaque tokens are introspectable.
Logout
GET /logout— the discoveryend_session_endpoint. Acceptspost_logout_redirect_uri+state; redirects there only if the URI is registered.POST /logout— first-party logout of the current session.
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