AUTH4D-12 OAuth authorization code and OIDC endpoints
All tenant operations use the tenant ID from a discovery supplied tenant_id endpoint query or
form value. The Worker resolves the customer tenant and client from primary-consistent D1 reads;
it never derives tenant identity from the request host. A registered redirect URI is compared as
the exact request string before the service redirects to it.
Discovery and keys
GET /t/{tenantId}/.well-known/openid-configurationGET /t/{tenantId}/.well-known/jwks.json
Discovery returns the tenant issuer {AUTH_ORIGIN}/t/{tenantId}, configured authorization,
token, UserInfo and revocation endpoints, the tenant JWKS URL, RS256, S256, and the code response
type. It advertises authorization-code and refresh grants only; device authorization is omitted
until its endpoint is implemented. The token, authorization, UserInfo, and revocation endpoint
URLs include the tenant ID as a query parameter. JWKS contains only public signing-key fields.
Authorization and consent
GET /oauth/authorize accepts tenant_id, response_type=code, client_id, redirect_uri,
scope, state, code_challenge, code_challenge_method=S256, and optional nonce. Duplicate
and unsupported parameters fail. The client must be enabled, allow the authorization-code grant,
allow every requested scope, and have the exact redirect URI registered. Native clients cannot use
this flow. Unsupported response types, including implicit, fail without issuing a code.
The API creates a ten-minute tenant-scoped browser flow, binds it to a random HttpOnly, Secure,
SameSite=Lax cookie, and redirects to the hosted login page with a non-secret flow_id. Login
providers continue through:
POST /oauth/authorize/continue
Origin: {AUTH_ORIGIN}
Content-Type: application/json
Cookie: __Host-auth4_flow_{flowId}=...
{"tenant_id":"ten_…","flow_id":"flw_…","decision":"approve",
"proof_id":"prf_…","proof_purpose":"discord_login"}
The continuation consumes the flow and identity proof once. It accepts only email-verification or
Discord-login proofs tied to the same tenant, client, browser flow and active user. An existing
browser session may supply the identity instead. decision=deny returns access_denied; approval
persists the explicit consent scopes and returns the authorization code and original state to the
registered redirect URI. Consent and one-time proof values are never accepted as caller-supplied
identity claims.
Authorization codes are random, tenant and client bound, expire after 60 seconds, and are stored only as hashes in the atomic state authority. The code is consumed before PKCE verification, so a wrong verifier cannot be retried. Token exchange requires the same exact redirect URI and an RFC 7636 verifier whose S256 digest matches the stored challenge.
Token, UserInfo and revocation
POST /oauth/token accepts URL-encoded authorization-code or refresh-token grants. Public clients
identify themselves with client_id; confidential clients use client_secret_basic, and the
stored secret hash is checked before the grant is processed. Body-based client secrets are rejected.
The device-code grant is delegated to the device authorization feature (AUTH4D-16). Password and
all other grants return unsupported_grant_type. Refresh scope overrides are rejected
because the session service issues the original granted scope set.
Authorization-code exchange delegates session creation, five-minute RS256 access and ID token issuance, browser-cookie handling, and optional rotating refresh tokens to the session service. The ID token uses the tenant issuer and client audience and carries the authorization request nonce.
GET /oauth/userinfo requires a verified Bearer access token. It returns sub and, only for the
email scope, a tenant identity’s verified email claims. Access-token and ID-token validators are
separate. POST /oauth/revoke accepts URL-encoded token revocation and returns success for unknown,
expired, or already revoked tokens as required by RFC 7009.
POST /oauth/logout is the hosted-login logout extension. It accepts JSON containing the tenant,
client, optional signed id_token_hint, and an optional exact registered post-logout redirect URI
and state. The request must have the configured auth origin. A matching browser session is revoked
and its cookie is cleared; an invalid ID token hint or unregistered redirect is rejected.
Runtime binding
The OAuth feature consumes the session library through a separate SESSION_STATE SQLite Durable
Object binding and loads its RS256 signing ring from the AUTH_SIGNING_KEY_RING Worker secret. The
current frozen API composition does not yet declare or register SESSION_STATE; the session
composition owner must wire SessionStateStore before enabling the token, UserInfo, revocation, and
logout paths in a deployed Worker. Missing session authority or signing material fails closed.
Source: docs/api-spec/auth4d-12.md