Revocation and Sign-Out
Revoking tokens and terminating user sessions are distinct operations in ahdapa. This chapter covers both.
Token revocation (POST /revoke, RFC 7009)
The revocation endpoint accepts an access token or a refresh token. The client must authenticate using the same method it uses at the token endpoint.
Revoke a refresh token
Revoking a refresh token invalidates its entire family. Any subsequent use of
any token in that family — including tokens issued by previous rotations —
returns invalid_grant.
curl -s -X POST https://idp.example.com/revoke \
-u "CLIENT_ID:CLIENT_SECRET" \
-d "token=BASE64URL_REFRESH_TOKEN" \
-d "token_type_hint=refresh_token"
Internals: the server sets max_index = u64::MAX for the family in the CRDT
and persists this to the database immediately. The change is gossipped to all
cluster nodes. Any node that receives a subsequent refresh request for this
family will reject it.
Revoke an access token
Access tokens can be revoked via the same endpoint:
curl -s -X POST https://idp.example.com/revoke \
-u "CLIENT_ID:CLIENT_SECRET" \
-d "token=BASE64URL_ACCESS_TOKEN" \
-d "token_type_hint=access_token"
Internals: the server extracts the jti and exp claims from the JWT
without re-verifying the signature and inserts them into the
crdt_revoked_access_tokens LWW-map. The entry is gossiped to all
cluster nodes immediately. Token introspection on any node will return
active: false for that jti until the access token’s natural expiry.
Expired entries are pruned automatically from both memory and the database
on each gossip cleanup tick, so the blocklist does not grow unboundedly.
Resource servers that validate access tokens locally (JWK-set signature
check only) will not see the revocation unless they call /introspect.
Use short access token lifetimes (tokens.access_token_ttl) together
with revocation for defence-in-depth against compromised tokens.
Response
The revocation endpoint always returns 200 OK, even for unknown or
already-expired tokens (RFC 7009 §2.2). This prevents oracle attacks that
would let an attacker determine which tokens are valid.
HTTP/1.1 200 OK
Error responses
Authentication failures return 401 Unauthorized before reaching the
revocation logic:
{"error": "invalid_client"}
Rate limit exceeded returns 429 Too Many Requests.
Session revocation (admin API)
To revoke all sessions for a specific user — for example after a security incident or account lockout — use the refresh families admin API:
# List all refresh families for a given subject
curl -s https://idp.example.com/api/admin/refresh-families -b session.jar \
| python3 -m json.tool
# Revoke a specific family
curl -s -X DELETE \
https://idp.example.com/api/admin/refresh-families/FAMILY_ID \
-b session.jar
This requires the clients:write RBAC permission.
RP-initiated logout (OIDC Session Management)
Relying parties can initiate logout by redirecting the user’s browser to the
/logout path:
GET https://idp.example.com/logout
?id_token_hint=ID_TOKEN
&post_logout_redirect_uri=https%3A%2F%2Fapp.example.com%2Flogout-complete
&state=OPAQUE_STATE_VALUE
What happens server-side:
- The session cookie is cleared.
- If
id_token_hintis provided and parseable, the corresponding refresh token family is looked up and revoked. - The user is redirected to
post_logout_redirect_uri(if provided), with thestateparameter appended.
post_logout_redirect_uri does not need to be pre-registered; any HTTPS URL
is accepted.
Front-channel vs. back-channel logout
ahdapa provides RP-initiated logout as described above. Formal OIDC front-channel logout (iframes in the browser) and back-channel logout (HTTP callbacks from the IdP to RPs) are not implemented. Use token revocation and short access token lifetimes to limit the exposure window when a session ends.
Best practices
- Always call
POST /revokewith the refresh token when the user explicitly signs out of your application. This propagates across all cluster nodes via CRDT gossip. - Keep access token lifetimes short (
tokens.access_token_ttl). Even with revocation, resource servers that do local JWT validation won’t honour the blocklist — short lifetimes remain the primary mitigation. - Use
offline_accessonly when persistent access is genuinely required. Omitting it means no refresh token is requested and the user must re-authenticate after the session expires. - Revoke the refresh token on sign-out even when you also redirect to the logout endpoint — belt-and-suspenders against the user manually returning to your app before the session cookie expires.