Skip to main content

Token revocation endpoint (RFC 7009)

The rule

  • POST as.oid4pay.com/oauth/revoke accepts the token to revoke (AT or RT) plus client authentication
  • Auth: the token's owning client via private_key_jwt, OR the principal via Wallet Portal session, OR admin via X-Internal-Key
  • Revokes the named token AND cascade-revokes its family (the RT lineage; for AT, the originating RT's family)
  • Cascade-revokes any mandate the family was issued under (Mandate.revoked_at set)
  • Emits oid4ac.mandate.revoked SSF event
  • Advertised in AS metadata as revocation_endpoint

Wire shape

POST /oauth/revoke HTTP/1.1
Host: as.oid4pay.com
Content-Type: application/x-www-form-urlencoded

token=<AT or RT>
&token_type_hint=refresh_token
&client_id=<agent_client_id>
&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
&client_assertion=<private_key_jwt>

200 OK
(empty body, per RFC 7009)

Three auth modes

ModeHeader / parameterUsed by
Client-ownedclient_assertion=<private_key_jwt>The agent revokes its own tokens.
Principal-ownedWallet Portal session cookieThe principal revokes an agent's tokens from the wallet UI.
AdminX-Internal-Key: <hmac>Operator-initiated revocation (e.g. response to a compromised key).

Cascade semantics

  1. Revoking an AT: the AT's jti is added to the revocation set; subsequent presentations are rejected.
  2. Revoking an RT: the RT family is marked revoked; every AT issued from the family is killed; the underlying mandate is killed.
  3. Revoking a mandate: every RT family that depends on the mandate is killed; every AT in those families is killed; the mandate's credentialStatus bit is flipped (see W3C VC Status List).

Worked example: agent revokes its own token

curl -sS https://as.oid4pay.com/oauth/revoke \
  -d token=$RT \
  -d token_type_hint=refresh_token \
  -d client_id=$CLIENT_ID \
  -d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
  --data-urlencode client_assertion=$PKJ
# 200 OK with empty body
# SSF event oid4ac.mandate.revoked emitted to subscribed receivers

Worked example: principal revokes via wallet

// Inside wallet.oid4pay.com:
await fetch("https://as.oid4pay.com/oauth/revoke", {
  method: "POST",
  headers: { "content-type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    token: mandateRt,
    token_type_hint: "refresh_token",
  }),
  credentials: "include",
});

RFC 7009 silence on unknown tokens

RFC 7009 §2.2 requires the revocation endpoint to return 200 even when the token is unknown (otherwise it leaks information about which tokens exist). OID4Pay's AS conforms: a syntactically valid request always returns 200.