Skip to content

Introspection and revocation

Introspection describes an access token to the client it was issued to and to nobody else, as JSON or as a signed JWT (RFC 7662, RFC 9701). Revocation lets a client give back a token it holds, and answers the same way whether the token existed or not (RFC 7009). Both are POST endpoints of the protocol plane, and both authenticate the client first, exactly as the token endpoint does.

Introspection

POST /introspect, the introspection_endpoint of the discovery document, with the token in the token parameter of the form body. token_type_hint is accepted and ignored.

The caller is a confidential client. It authenticates with one of the methods announced in introspection_endpoint_auth_methods_supported, which are the wired methods that authenticate a client, every one but none. A public client is refused with invalid_client, status 401, since it has nothing to authenticate with.

A token is described to the client it was issued to, and to nobody else (RFC 7662 §2.2, §4). Everything else is answered {"active": false} and nothing more, whatever the reason:

  • a value the provider did not issue, or no longer holds;
  • a token issued to another client;
  • an expired token;
  • a token of a client no longer registered;
  • a refresh token, introspection describing access tokens only;
  • for a token somebody signed in for, and a token exchanged from one: a session that has ended, a grant that has been revoked, or a person the provider no longer serves, an account that is disabled among them.

The rules are the ones UserInfo applies, read in one place for both shapes of token, so that an at+jwt of a session ended at the end session endpoint is described as inactive from that moment, where a resource server that verifies it offline accepts it until it expires.

A token that descends from no sign-in is asked none of the last three questions. A token a client holds for itself names nobody. A token of the JWT bearer grant, and a token exchanged from one, names a person without descending from a sign-in of theirs: it is described as active until it expires, whatever became of that person's account in the meantime.

What an active token is described with

active first, then the members of RFC 7662 §2.2 and those of RFC 9068 §2.2.1, whatever the shape of the token, at+jwt or opaque:

Member What it says
active true, a JSON boolean
scope The scopes of the token, space-separated
client_id The client the token was issued to, which is the caller
token_type DPoP for a token bound to a key, Bearer otherwise
exp, iat When the token expires and when it was issued
sub The subject identifier the token was written with. For a token somebody signed in for and a token of the JWT bearer grant, the one its client knows the person by, public or pairwise; for an exchanged token, the sub of the token presented at the exchange, as the client of that token knew it; for a token a client holds for itself, the client
aud The client, or the resources the token was asked for (RFC 8707 §2): a string for one value, an array for several
iss The issuer
auth_time, acr, amr, sid The sign-in the token descends from. Absent from a token a client holds for itself, which descends from none (RFC 6749 §4.4)
grant_id The grant the token descends from, a UUID
act Who holds the token on behalf of its subject, the most recent actor outermost (RFC 8693 §4.1). Absent when nobody does
authorization_details What the token is good for beyond its scopes (RFC 9396 §9). Absent when it carries none
cnf What the token is bound to, jkt or x5t#S256 (RFC 7800 §3.1). Absent from a bearer token

A token bound to a certificate is described whatever certificate the caller presented: the client may introspect its token however it authenticates today, and the cnf is what a resource server needs to make its own check (RFC 8705 §3.2). Sender-constrained tokens says what to compare it with.

A signed answer

A request whose Accept header names application/token-introspection+jwt is answered with a JWT of that type (RFC 9701 §4, §5), whatever the client registered:

Where Member Value
Header typ token-introspection+jwt
Header alg, kid The algorithm the provider signs with by default, RS256 in the bundle, and the key that signed
Claims iss The issuer
Claims aud The client that asked
Claims iat When the request was received
Claims token_introspection The members above, {"active": false} included

The answer is signed and not encrypted. A resource server verifies it against the JWK Set of the provider, whose URL it reads in the jwks_uri of the discovery document. The document announces the signing algorithms the provider is wired with in introspection_signing_alg_values_supported, and the answer is signed with the default one among them.

Either answer is served with Cache-Control: no-store and Pragma: no-cache, as application/json or as application/token-introspection+jwt.

The journal

An answer that hands a person over is an act of their journal. An introspection that describes an active token somebody signed in for, or a token exchanged from one, tells a third party who that token names, so it is written down, introspection.answered, naming the client that asked and the person. An answer {"active": false} writes nothing, since it describes nobody. The act is written for a token that descends from a sign-in and for no other: the description of a token a client holds for itself writes nothing, and neither does the description of a token of the JWT bearer grant or exchanged from one.

Revocation

POST /revoke, the revocation_endpoint of the discovery document, with the token in the token parameter. token_type_hint is accepted and ignored: the provider recognises what it is given.

Every client may revoke its own tokens. It authenticates with the methods announced in revocation_endpoint_auth_methods_supported, which are the methods of the token endpoint, none included: a public client names itself in client_id and revokes what was issued to it.

The token What the provider does The answer
An opaque access token issued to the caller Removes it: it is refused everywhere from then on 200
A refresh token issued to the caller Revokes it, and removes the opaque access tokens of its grant (RFC 7009 §2.1) 200
A refresh token already revoked Nothing 200
An at+jwt that verifies, whichever client it was issued to: signed by a published key of the tenant, not expired, issued to a client still registered, whatever became of its session, its grant or its person Nothing: a self-contained token cannot be taken back (RFC 7009 §2.2.1) 400, unsupported_token_type
An at+jwt that does not verify: expired, or not issued by the provider Nothing 200
Any other token issued to another client, or a value the provider does not know Nothing (RFC 7009 §2.2) 200

A 200 has an empty body, with Cache-Control: no-store and Pragma: no-cache, so that the answer says nothing about whether the token existed.

What a revocation does not reach. An at+jwt issued under the grant of a refresh token that was revoked stays valid until it expires, at a resource server as at the endpoints of the provider: the provider keeps no list of the self-contained tokens it would refuse, and its lifetime is what bounds it, ten minutes unless tokens.access_token_ttl says otherwise. A client whose access tokens must be taken back at once is declared with access_token_format: opaque.

Every revocation is an act, token.revoked, in the journal of the person the token was issued for; the revocation of a token a client held for itself names nobody.

Revocation ends one token and what depends on it. What takes back everything issued under a session is the end of that session, at the end session endpoint or anywhere else, and what takes back the grants, the refresh tokens and the opaque access tokens a client holds for a person is that person taking back their consent, on the page of their account or with beffroi:user:revoke-consent. In both cases an at+jwt is refused by the provider from that moment, and accepted by a resource server that verifies it offline until it expires.

Refusals

Both endpoints answer with the errors of RFC 6749 §5.2, as JSON, with Cache-Control: no-store and Pragma: no-cache:

Error Status When
invalid_client 401 The client does not authenticate, or is a public client at the introspection endpoint. A client that tried the Authorization header is answered WWW-Authenticate: Basic realm="<issuer>"
invalid_request 400 The token parameter is missing, a parameter is repeated, the request cannot be read as a client authentication, or it carries a client certificate in a header
unsupported_token_type 400 At the revocation endpoint, an at+jwt that verifies

Introspection never answers an error for a token that is not active: that is {"active": false}. Every failed client authentication is an act, client_authentication.failed; errors says what the journal keeps of it.