OAuth 2.0 Token Introspection (RFC 7662), conn-free core.
Given a presented token, decide whether it is currently active and, if so,
describe it with the RFC 7662 response members. This is the deliberate
introspection entry point; the transport layer authenticates the caller and
decides (by content negotiation) whether to return the plain JSON response or
a signed JWT (Attesto.SignedIntrospection, RFC 9701 / FAPI 2.0 Message
Signing §5.5). Nothing here touches a conn.
What "active" means (RFC 7662 §2.2)
Access tokens are stateless JWTs: a token is active iff it passes the full access-token verification (
Attesto.Token.verify/3- signature, issuer, audience, temporal, required claims, principal, andtyp == "access"), the same checks a resource server applies. The ONE check skipped is the sender-constraint binding (the proof-key match): thecnfis part of who may use the token, verified when it is presented, and the introspecting caller holds no proof key - so introspection passesrequire_confirmation_binding: false. Thecnfshape is still validated, and the binding is echoed in the response so a resource server can check it (RFC 7662 / RFC 8705 §3.2 / RFC 9449).Refresh tokens are opaque, stored secrets: a refresh token present in the store, unconsumed, and unexpired is reported active, a consumed (rotated) or expired one inactive. The stored record's own data contract (
Attesto.RefreshTokenbuild context) carries the subject, granted scope, presenting client, and optional DPoP binding, so those RFC 7662 members are surfaced when present (sub/scope/client_id/cnf) - for a resource server, and so an:authorizepolicy can decide per token. A store that does not populate them yields the minimalactive+expresponse.Anything else - a malformed token, a forged signature, an expired token, or one absent from the store - is reported inactive (
%{"active" => false}), never an error: RFC 7662 §2.2 forbids a token-existence oracle.
token_type_hint (RFC 7662 §2.1) only reorders which check is tried first; an
unmatched hint still falls through to the other.
Summary
Functions
Introspect token, returning the RFC 7662 response map (always active,
plus the token's members when active). Never returns an error.
Types
Functions
@spec introspect(Attesto.Config.t(), String.t(), opts()) :: response()
Introspect token, returning the RFC 7662 response map (always active,
plus the token's members when active). Never returns an error.
Options:
:refresh_store- anAttesto.RefreshStoremodule to consult for opaque refresh tokens; when absent, only access tokens are introspected.:token_type_hint-"access_token"or"refresh_token"(RFC 7662 §2.1); reorders the attempts, never restricts them.:authorize- a 1-arity predicate(response -> boolean)consulted with the active response before it is returned (RFC 7662 §4 / RFC 9701 §5: the AS MAY restrict which tokens a caller may introspect). The transport layer captures the authenticated caller identity in this closure; if it does not returntrue- or it raises - the response is downgraded to%{"active" => false}so a caller not authorized for the token learns nothing about it (FAPI: a regular client querying introspection is a leakage risk). When absent, every authenticated caller may introspect any token (the single-trust-domain default).:now- the reference time (Unix seconds orDateTime), for tests.