Obexal Docs

Docs/Authentication/Validate tokens

Validate tokens in your API

Verify Obexal access tokens on your resource server: local JWKS validation versus RFC 7662 introspection, revocation, lifetimes and refresh token rotation.

Your API receives access tokens issued by Obexal and must decide, on every request, whether to trust them. There are two ways to do that: validate the JWT locally against the public keys, or ask Obexal through the introspection endpoint. This page covers both, plus revocation and refresh token hygiene.

Access tokens are JWTs signed with RS256 (header typ: at+jwt). Refresh tokens are opaque strings: they cannot be validated locally, only introspected or exchanged.

Note

Examples use accounts.obexal.com, the default domain. With a custom domain, that domain is the issuer.

Local validation with JWKS

Any standard JWT library can validate an Obexal access token:

  1. Fetch the JWKS from https://accounts.obexal.com/.well-known/jwks.json and cache it (the response is served with a short public cache). It contains the active key and retired keys that have not yet expired, so rotation is transparent.
  2. Select the key by the kid header and accept RS256 only: reject none and any HS* algorithm (algorithm confusion defense).
  3. Verify the signature, the iss claim (your issuer URL) and exp.
  4. Check that the JWT header typ is at+jwt: this rejects an ID token replayed as an access token.
  5. Enforce your own authorization: scope, and aud where relevant (delegated agent tokens can be bound to a specific resource server via aud).

Introspection (RFC 7662)

POST /oauth/introspect returns the live state of a token. The endpoint is protected: only an authenticated confidential client may call it (a public client receives 401 invalid_client), and a client can only introspect its own tokens. Any other token, and any unknown, expired or revoked token, answers {"active": false} with no further detail. Both access tokens (JWT) and refresh tokens (opaque) are accepted.

curl -sS -X POST https://accounts.obexal.com/oauth/introspect \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d "token=$ACCESS_TOKEN"
{
  "active": true,
  "token_type": "access_token",
  "client_id": "app_a1b2c3",
  "scope": "openid profile email",
  "sub": "8f2c1e9a-4b7d-4c2e-9f1a-3d5e7b9c0a12",
  "username": "alice@example.eu",
  "iss": "https://accounts.obexal.com",
  "aud": "app_a1b2c3",
  "exp": 1751450000,
  "iat": 1751449400,
  "act": { "sub": "agent_7d4e" }
}

The act member only appears on delegated tokens: it identifies the AI agent acting on behalf of the user in sub, chained for nested delegation. See Delegation with Token Exchange.

Choosing between the two

  • Local JWKS validation costs no network call per request and scales with your API. Its limit: a token stays valid until exp, so revocation is only as fast as the token lifetime (10 minutes by default).
  • Introspection returns the live truth: it reflects revocations immediately, including the kill switch of a disabled AI agent, whose tokens report active: false even before they expire. It is also the only option for refresh tokens. Its cost: one HTTP call and a client secret on your resource server.

A common pattern: validate locally on the hot path, and introspect for sensitive operations where immediate revocation matters.

Lifetimes and revocation

TokenDefault lifetimeForm
Access token10 minutesJWT RS256, typ: at+jwt
ID token10 minutesJWT RS256
Refresh token30 daysOpaque, 256 bits, hash stored server-side

On a self-hosted deployment these are configurable; see Configuration.

POST /oauth/revoke (RFC 7009) revokes a refresh token. The call is client-authenticated and always returns 200, even for an unknown or already revoked token (idempotent by design):

curl -sS -X POST https://accounts.obexal.com/oauth/revoke \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d "token=$REFRESH_TOKEN" -d 'token_type_hint=refresh_token'
# 200, always

Access tokens are not individually revocable: they simply expire, which is why their lifetime is short. When you need instant invalidation on top of that, use introspection.

Refresh tokens: rotation and replay detection

Refresh tokens rotate on every use: the token you present is revoked and a new one is issued in the same chain, with the same scopes or a requested subset. Store the new token and discard the old one.

Presenting an already revoked refresh token is treated as a compromise signal, not a retry: Obexal revokes the entire chain for that user and client, refuses the request with invalid_grant, and records a critical security incident (refresh_token_replay) surfaced in the admin console.

Warning

Treat invalid_grant on a refresh call as a re-authentication event for the user. Never retry the old token in a loop.