Beta

Create a tenant

A new tenant starts with its own users, OAuth settings, audit history and logs. You are its first Tenant Admin.

BTL Admin

Token introspection

The photo service has decided that its access tokens should be references, not JWTs. demo-access-token-7 is now exactly what it looks like: a random string that means nothing outside the authorization server's records. The service can revoke one and know it stops working, and nothing about your account travels inside the token.

That leaves the photo API with nothing to verify. There is no signature to check and no claim to read. To decide whether the printer may see album 42, the API has to ask the authorization server what the token stands for.

Asking the authorization server

The question goes to the authorization server's introspection endpoint. All domains, credentials, and tokens in these examples are fictional, and the Basic header represents photo-api:demo-only-not-a-real-secret:

POST /introspect HTTP/1.1
Host: auth.photos.example
Authorization: Basic cGhvdG8tYXBpOmRlbW8tb25seS1ub3QtYS1yZWFsLXNlY3JldA==
Content-Type: application/x-www-form-urlencoded
Accept: application/json

token=demo-access-token-7
&token_type_hint=access_token

The token travels in a form-encoded body, never in the URL, for the same reasons it stayed out of URLs when the printer called the API. token_type_hint is optional. It tells the server where to look first, and if the token is not found there, the server must search its other token types.

The Authorization header belongs to the photo API, not the printer. The API is registered with the photo service as a confidential client of its own, photo-api, and authenticates with any of the usual client authentication methods. The endpoint has to be protected. If anyone could call it, they could submit guessed strings until one came back active, an attack known as token scanning. Authentication also tells the authorization server which API is asking, and that shapes the answer: it can reply only about tokens meant for that API, and include only what that API needs to know.

The printer calls the photo API with a reference access token. The photo API authenticates as its own client, photo-api, and sends the token to the authorization server's introspection endpoint. The server checks that the token exists, has not expired or been revoked, and may be seen by this API, then returns active true with the subject, client, audience, scope and expiry. The photo API checks the audience, the scope and access to album 42, caches the answer briefly and never past the token's expiry, and returns the photos. If the server answers active false, the API returns 401 with invalid_token. If the server does not answer, the API refuses the request with 503 rather than accepting the token. The printer calls the photo API with a reference access token. The photo API authenticates as its own client, photo-api, and sends the token to the authorization server's introspection endpoint. The server checks that the token exists, has not expired or been revoked, and may be seen by this API, then returns active true with the subject, client, audience, scope and expiry. The photo API checks the audience, the scope and access to album 42, caches the answer briefly and never past the token's expiry, and returns the photos. If the server answers active false, the API returns 401 with invalid_token. If the server does not answer, the API refuses the request with 503 rather than accepting the token.
The photo API asks the authorization server about the printer's token, authenticating as itself. An active answer still goes through the API's own scope and album checks.

Reading the answer

For the printer's token, the authorization server answers:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "active": true,
  "iss": "https://auth.photos.example",
  "sub": "user-2048",
  "aud": "https://api.photos.example",
  "client_id": "photo-printer",
  "scope": "photos.read",
  "token_type": "Bearer",
  "iat": 1790845200,
  "exp": 1790845800
}

The fields carry the same meanings as the claims in a JWT access token. Only active is required. Which of the others appear is up to the authorization server.

For every token that should not be accepted, the answer is the same:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "active": false
}

Expired, revoked, never issued, or one this API is not allowed to ask about, which can include a token meant for another API: all of them produce "active": false, and the specification says the server should not attach a reason. That is deliberate. A caller told "revoked" rather than "never existed" learns something about the server's records. The photo API passes the same uniformity on, answering the printer with invalid_token whatever the cause.

"active": true means the token is genuine and current. It does not mean this request is allowed. The API still checks that aud names it, when the response includes an audience. A server that tailors its answers to each caller may already have answered false for a token meant elsewhere, but the API does not depend on that. It then checks the scope against the operation and the album against the subject, exactly as in Enforcing access at an API. Introspection replaces the signature and claim checks, not the authorization decision.

Some deployments need a record of the answer that can be verified later, not just one received over a protected connection. A separate standard lets the authorization server return the same information as a JWT it has signed.

Caching and freshness

An introspection call on every API request adds a network round trip to each one, and it makes the authorization server part of every request path. The API can cache answers, at a price.

A cached "active": true keeps working even if the token is revoked a moment later. The cache lifetime is exactly the window in which a revoked token still works at this API. The specification leaves the balance to each deployment, with one firm rule: an answer that includes exp must not be cached past that time. Within that limit, the photo API might keep answers for a minute when photos are being read, and introspect afresh before every deletion, so a revoked token could at most read for another minute and never delete anything. The cache is keyed by a hash of the token rather than the token itself, so a memory dump or a debugging tool does not reveal working tokens.

The harder question is what to do when the authorization server does not answer. A timeout, a server error, or a refused connection says nothing about the token. Accepting the request anyway, failing open, would let any string through during an outage, including tokens revoked minutes before. The photo API fails closed: it refuses requests it cannot verify.

The introspection standard leaves the API's answer in this case to the API. The photo API's choice is 503 Service Unavailable, which describes the refusal better than a 401. The token may be perfectly good, and telling the printer it is invalid would send the printer off to replace a token that was never the problem. Answers already in the cache can still be used until their normal expiry, but not stretched beyond it to ride out the outage.

What the response reveals

An introspection response can describe a person: an account identifier, sometimes a username, which client they connected, and what they allowed it to do. Every API that can introspect learns this about every token presented to it. The specification requires measures against disclosure to unintended parties, and the simplest measure is to send less.

The photo service tailors each answer to the caller. The photo API receives the scopes that mean something at the photo API, and nothing about your other connections. A service that wants to stop its APIs from correlating activity can go further and give each API a different subject identifier for the same person, so the identifier the sharing API sees for you has no visible connection to user-2048. A field that is never sent never needs protecting.

The API handles what it receives with matching care. The response, like the token in the request, does not belong in ordinary logs. A record that a token was active, for which client and subject, and what the API decided, answers the questions a later investigation will ask.

Introspection makes a revocation visible to an API within one cache period. The revocation itself starts elsewhere, often with the client that holds the token.

Try it in the Lab

PUT IT INTO PRACTICE

Check your understanding

Try these questions before moving on. If an answer isn't right, use the feedback and try again.

0 of 2 answered correctly

Enable JavaScript to answer these questions and save progress in this browser.

QUESTION 1 OF 2The photo API introspects a token and receives active true with scope photos.read. The request is DELETE /photos/7. What should the API do?

QUESTION 2 OF 2The authorization server's introspection endpoint times out. What should the photo API do with the request?

We value your privacy

We use cookies and similar technologies to enhance your browsing experience, and analytics to understand our traffic. By clicking "Allow All", you consent to optional analytics. Cookie Policy

Learn identity