Skip to content

Introspect

This page helps your API ask the auth server whether an access token is still active, with the introspection endpoint, /auth/introspect.

It’s the RFC 7662 token introspection endpoint, the introspection_endpoint in the discovery document. A signature check tells your API a token was issued and hasn’t expired. Introspection also tells it whether the token was cut off since: its session ended, its grant was revoked, its user or its client was disabled.

  1. Give your API a confidential client, one with a client secret, listed for its resource, as Protect an API shows. See Introspection resources.

  2. From your API, send the access token it received, with the client’s credentials:

    POST /auth/introspect HTTP/1.1
    Host: auth.example.com
    Content-Type: application/x-www-form-urlencoded
    Authorization: Basic cHJvZHVjdC1hcGktaW50cm9zcGVjdG9yOm15LXNlY3JldA==
    token=eyJhbGciOiJSUzI1NiIsImtpZCI6Ii4uLiIsInR5cCI6ImF0K2p3dCJ9...
  3. Read whether it’s active:

    {
    "active": true,
    "scope": "openid product-api:read",
    "client_id": "my-web-app",
    "sub": "8a1f7c2e-5b3d-4e6f-9a0b-1c2d3e4f5a6b",
    "aud": ["product-api", "authserver"],
    "iss": "https://auth.example.com",
    "exp": 1760000300,
    "iat": 1760000000,
    "nbf": 1760000000,
    "jti": "9a6f1c1e-2d0b-4f3e-8a55-0c1b7a3e2f44",
    "token_type": "Bearer"
    }

    Anything else is answered with active alone, and your API refuses the request:

    {
    "active": false
    }

Still check scope for the permission the operation needs, as Protect an API describes.

An access token bound to a key with DPoP is answered with token_type DPoP, and its cnf, whose jkt is the thumbprint of the key:

{
"active": true,
"scope": "product-api:read",
"client_id": "my-spa",
"sub": "8a1f7c2e-5b3d-4e6f-9a0b-1c2d3e4f5a6b",
"aud": "product-api",
"iss": "https://auth.example.com",
"exp": 1760000300,
"iat": 1760000000,
"nbf": 1760000000,
"jti": "9a6f1c1e-2d0b-4f3e-8a55-0c1b7a3e2f44",
"token_type": "DPoP",
"cnf": {
"jkt": "0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I"
}
}

Introspection doesn’t check the proof: your API does, as Accept DPoP-bound tokens describes. Send no proof to /auth/introspect: the endpoint ignores a DPoP header.

An access token is active while it’s live and its aud names a resource on the calling client’s introspection resources. Live means it hasn’t expired, its iss is the current issuer, its user and its client exist and are enabled, and nothing has ended it since, such as its session ending, its user’s credentials changing, or it or its grant being revoked. The auth server’s own /userinfo and APIs make the same checks. See Ending sessions.

A refresh token is active only to the client it was issued to, while it’s live and the flow that issued it is still enabled for that client. Live means nothing has ended it: it hasn’t expired or been rotated or revoked, its user is enabled, and neither its session ending nor its user’s credentials changing has ended it. So your app’s backend can find out whether its refresh token is still good without using it. Introspecting it spends nothing, and the refresh token keeps working.

Active doesn’t promise the next refresh succeeds. A refresh checks the user’s consent and permissions, and your client’s scope allowances, when it’s made, so a refresh with an active token can still be refused with invalid_grant.

Every other token gets {"active": false}: one that’s expired, revoked, forged or malformed, an ID token, an access token for a resource the client doesn’t list, and another client’s refresh token. The answer never says which, as RFC 7662 section 2.2 recommends.

The members are read from the token, and they’re the same for every client that may see it. A user’s profile claims aren’t among them: call /userinfo for those.

Member Access token Refresh token
active true true
scope The token’s scope The token’s scope
client_id The client the token was issued to Your client
sub The user, or for a client credentials token, the client The user
aud The token’s aud, a string or an array Not sent
iss The issuer The issuer
exp, iat When the token expires and was issued, in seconds since the epoch The same
nbf When the token became usable Not sent
jti The token’s identifier The token’s identifier
token_type DPoP for a DPoP-bound token, Bearer otherwise Not sent
cnf For a DPoP-bound token only: jkt, the thumbprint of its key Not sent

/auth/introspect takes a POST, with the parameters in an application/x-www-form-urlencoded body. Each parameter goes in once.

Parameter Required What it does
token Yes The access token or refresh token to ask about
token_type_hint No Ignored. The auth server reads the token’s type from the token

The calling client authenticates exactly as at the token endpoint, with client_secret_basic or client_secret_post. See Client authentication for the rules and the refusals, which are the same here.

A public client can’t introspect, since it has no secret to authenticate with.

The auth server’s log has a client authentication refused at the introspection endpoint warning for each refusal. See Logs.

Status When
200 OK The token is active, or {"active": false}
400 Bad Request, invalid_request token or client_id is missing, a parameter or the Authorization header is repeated, the body can’t be read, or the secret is sent both ways
401 Unauthorized, invalid_client The client couldn’t be authenticated, or it’s a public client
500 Internal Server Error, server_error The auth server couldn’t check the token. That says nothing about the token: try again, or refuse the request

Every answer carries Cache-Control: no-store. If your API caches answers itself, keep each one no longer than you can accept a revoked token being honored, and never past the token’s exp.

An error is a JSON body with error and error_description, as on the token endpoint.