Trust and validation
A token exchange turns one token into another, which makes the token endpoint worth attacking. If it exchanged any token it was shown, anyone holding a stolen access token could turn it into tokens for services that token was never meant to reach.
The specification leaves the trust model to each deployment, so the checks that prevent this belong to the authorization server's own policy. The downstream API then makes checks of its own, because a token that came out of an exchange deserves no more trust than any other.
Who may exchange
Client authentication comes first. The specification lets each server decide whether to accept exchanges from unauthenticated clients, and warns of the cost: without client authentication, anyone who obtains a token can use the token service to turn it into other tokens. The photo service requires authentication, and permits the token exchange grant only for clients registered to use it. photo-api is one. The printer is not, so if it sent the same exchange with its own perfectly valid token, the server would refuse with unauthorized_client.
The subject token must also belong with the client presenting it. The printer's token was issued for the photo API, so the photo service accepts it as a subject token only from photo-api, the client registered for that API. Without this check, a client allowed to exchange tokens for one purpose could exchange any token that reached it. The storage service, which exchanges tokens of its own, could take a photo API token that a caller sent it by mistake and swap it for access its real recipient never asked for. Where a subject token carries may_act, the server compares it with the client or the actor token as well.
All identifiers in this example are fictional. The photo service keeps these decisions in an exchange policy for each client, much as it keeps a registration:
Client: photo-api
Grant types: urn:ietf:params:oauth:grant-type:token-exchange
Subject tokens accepted: access tokens from https://auth.photos.example
whose audience is https://api.photos.example
Targets: https://storage.photos.example
Scope mapping: photos.read to storage.read, photos.delete to storage.delete
Actor: record this client in act (delegation)
Lifetime: 60 seconds, never past the subject token's expiry
Each line becomes a check. Anything the policy does not mention is refused.
Validating an exchange request
With the client authenticated and permitted to exchange, the authorization server works through the request:
- Read the subject token by its type. Refuse a type this client may not present, or a token that does not parse as the type claimed.
- Validate it as its type requires. For its own access token, the photo service checks the signature with its own key, that the token has not expired or been revoked, and that its audience is the API registered as the client presenting it. A token from another issuer needs a trust arrangement set up in advance, exactly as for assertion grants: which issuers, which keys, and which subjects each may speak for.
- Validate any actor token the same way, and check that this actor may act for this subject, through
may_actor the server's own policy, such as an agent's current role. - Check the target. This client must be allowed to obtain tokens for the named resource or audience.
- Work out the scope. It must be no more than the policy maps from the subject token's scope, no more than was requested, and no more than the subject's own rights allow.
- Set the lifetime. Short, and never later than the subject token's own expiry.
- Decide how to record the actor, as delegation or impersonation, according to the policy for this client and target.
- Record the exchange, linking the subject token's identifier to the new token's.
The lifetime rule is the photo service's own. As Following a token exchange showed, the specification treats an exchange as a one-time event that leaves the two tokens unlinked, and it allows, without requiring, the old token's expiry to influence the new one's. The photo service turns that allowance into a rule and never lets an exchanged token outlive its subject token. Otherwise a chain of exchanges could keep your authority alive after the printer's own token had expired.
The same principle covers authentication details. If the printer's token recorded when and how you signed in, through claims such as auth_time and acr, the JWT access token profile says tokens derived from it, including by exchange, keep the original values. An exchange must not make a sign-in look more recent or stronger than it was. The specification also says deployments should put only the data the recipient needs into issued tokens, which can mean no more than a pseudonymous identifier for the person.
What the downstream API checks
The storage service receives an ordinary access token and validates it in the ordinary way, following the steps in Token formats and validation: an allowed algorithm, a typ of at+jwt, a signature from the photo service's key set, the exact issuer, its own audience, and expiry. Then it makes its own decision:
- The scope covers the operation, here
storage.readto read a file. - The object belongs to the subject.
photo-1187must be one ofuser-2048's files, or shared with that account. The storage service takes the subject from the validated token, never from a path or header the photo API supplies. - The actor is one it expects. The storage service accepts delegation tokens only when the current actor is
photo-api. A token about you with any other actor is refused, even with the right scope.
On which claims count, the specification is explicit. For access control, a recipient must consider only the token's top-level claims and the current actor, the party named in the outermost act. Earlier actors nested inside are informational. They belong in logs and investigations, not in access decisions.
The storage token is still a bearer token, and anyone who copied it during its minute could use it. Inside a platform, the tokens from Certificate-bound access tokens can tie it to the photo API's certificate, so that a copy is useless anywhere else. That binding is one more choice the authorization server can make at the moment of exchange, alongside audience, scope, and lifetime.