Docs
  • 0.1.0latest

Tokens

The token endpoint: codes redeemed once, five-minute access tokens addressed to one resource, ID tokens, acr and amr, refresh rotation with reuse detection, client credentials and DPoP.

POST <issuer>/oauth2/token

The token endpoint reads a form body of at most 16 KiB, refuses a repeated parameter or more than one client authentication method, and authenticates the client by its registered method before it reads the grant. Refusals carry the error code alone.

Authorization codes

Codes are stored by their SHA-256 digest only and are spent with one conditional write. The code is spent first and its bindings checked after (client, exact redirect URI, PKCE verifier, user, company), so a code redeems once whatever the outcome. Of any number of simultaneous redemptions exactly one succeeds. A code presented again is refused and revokes the refresh family it started.

The access token

An RFC 9068 JWT addressed to one resource, carrying only iss, aud, sub, client_id, scope, iat, exp and jti, valid for five minutes. sub is a pairwise identifier per client. Five minutes is also how long a revoked grant can still be used: an access token is never recalled.

The ID token

A code granted openid also redeems for an ID token (OpenID Connect Core §3.1.3.3): a JWT of type JWT, signed by the same key, addressed to the client alone, living five minutes. It states how the user signed in, as the code recorded it: auth_time, acr, amr, sid (the session's public id, never its cookie), the request's nonce when one was sent, and at_hash binding it to the access token beside it, under the same pairwise sub. It carries no profile or contact data.

acr and amr come only from the methods verified in the sign-in behind the code, never from what the account has enrolled or what the request asked for: the server reads no acr_values. A recovery code counts as a one-time code. A passkey verifies its user when the authenticator checked a PIN or a biometric. Passkeys sync between devices, so no level claims a device-bound key.

Methods verified in the sign-inacramr
passwordurn:tessaridb:acr:sfpwd
one-time or recovery code aloneurn:tessaridb:acr:sfotp
passkey that did not verify its userurn:tessaridb:acr:sfhwk
password and a one-time or recovery codeurn:tessaridb:acr:mfapwd, otp, mfa
password and a passkey that did not verify its userurn:tessaridb:acr:mfapwd, hwk, mfa
passkey that verified its userurn:tessaridb:acr:phrhwk, mfa
password and a passkey that verified its userurn:tessaridb:acr:phrpwd, hwk, mfa

sf is one factor category, mfa two different ones (something known and something held), and phr a passkey that verified its user, which the browser binds to this origin. A one-time code and a recovery code together are still one category.

Refresh tokens

A client registered for refresh tokens also receives one and redeems it at the same endpoint (RFC 6749 §6). Refresh tokens rotate on every use and are kept as digests only. The request is judged on the family's grant before the token is rotated, so a refused refresh leaves the token usable. The new access token may be narrowed to fewer scopes or one of the grant's resources, never widened. A token presented after it was rotated revokes its whole family. A refresh token lives at most 14 days unused and 30 days in all.

Client credentials

A confidential client registered for client_credentials (RFC 6749 §4.4; a public client cannot register it) gets a token of its own. It names its scopes within its ceiling, never openid since no user signed in, and one of the resources it registered. The token's sub is the client itself (RFC 9068 §2.2), and no refresh token is issued.

DPoP

A request carrying a DPoP proof, one DPoP header verified for this endpoint and spent once after every other check, receives a DPoP token naming the proof's key in cnf.jkt. A client registered with dpop_bound_access_tokens is refused without one, and a public client's refresh token keeps needing the key it was issued to. Without a proof the token is Bearer.