7 min read

What is a token?

A few lines of text, signed by a service the API already trusts. The API checks the text. It never has to trust whoever is carrying it.

This lesson builds on the one before it. If you arrived here first and a term is unfamiliar, lesson 1 is where they start, and each is in the glossary.

A key card, not a passport

A hotel key card is a fair picture of a token. The front desk checks who you are, once, and hands you a card. The card opens your room and the gym, in this hotel, until checkout. The door never asks for your passport. It checks the card.

An access token is the card. The front desk is called an authorization server: the one service that checks who people are, asks what they agree to, and issues tokens. The door is the API.

That is as far as the picture needs to go. What makes a token work is a signature.

What is written on it

Many access tokens are JWTs (said “jots”): three chunks of text joined by dots. You have probably seen one. Not every token is one, and the end of this lesson comes back to that. This lesson uses a JWT because you can read what it says.

The first two chunks are not encrypted. They are JSON, encoded so that they survive a trip through an HTTP header. Decode the middle one and you get a handful of lines, each called a claim:

The claims of an example access token, abbreviated
In the tokenWhat it saysWhich question
"iss": "https://auth.example"The issuer. The authorization server at auth.example signed this.Who said they could
"sub": "alice"The subject. It is about Alice.For whom
"client_id": "calendar-assistant"It was issued to this app.Who is acting
"aud": "https://calendar.example"The audience. It is meant for the calendar API and for nothing else.Who said they could
"scope": "calendar.read"The scope. It allows reading the calendar.Who said they could
"exp": 1794305400It stops working at 10:10, ten minutes after it was issued. The number is seconds since 1970.Who said they could

The third chunk is the signature. The authorization server computes it from the first two chunks with a private key that never leaves it, and publishes the matching public key. Anyone can check the signature. Only the authorization server can make one.

Try to break it

An API believes nothing in a token until it has checked it. There are five checks. The first button below sends the token as it was issued, and each of the other five does something to it. Before you press one of those, guess which check will catch it. One of them gets past all five.

What happens to the token

What happens to the token: Nothing: sent as issued

The token’s claims

{
  "iss": "https://auth.example",
  "sub": "alice",
  "client_id": "calendar-assistant",
  "aud": "https://calendar.example",
  "scope": "calendar.read",
  "exp": 1794305400
}

What the API compares them with

this API:  calendar.example
time now:  10:04
request:   GET /events?day=tomorrow
The API’s five checks, in order
From an issuer I trust?Yesiss is auth.example, which this API is set up to trust.
Signature intact?YesIt verifies with auth.example’s public key. Nothing has changed since it was signed.
Addressed to me?Yesaud is calendar.example. That is this API.
Still in date?Yesexp is 10:10. It is 10:04.
Covers this request?YesThe request reads events, and scope allows calendar.read.

All five pass, so the API believes the claims: these are Alice’s events, for the app she approved. It did not have to ask anyone at the moment of the request, and it did not have to trust the agent. The signature is the proof.

What happens to the token: Someone edits it

The token’s claims

{
  "iss": "https://auth.example",
  "sub": "alice",
  "client_id": "calendar-assistant",
  "aud": "https://calendar.example",
  "scope": "calendar.write",
  "exp": 1794305400
}

What the API compares them with

this API:  calendar.example
time now:  10:04
request:   DELETE /events/42
The API’s five checks, in order
From an issuer I trust?Yesiss still says auth.example.
Signature intact?NoThe claims no longer match the signature. Only auth.example’s private key could sign the new version.
Addressed to me?SkippedNot reached.
Still in date?SkippedNot reached.
Covers this request?SkippedNot reached.

Rejected with 401. Anyone can read a token. Only the service that signs it can change one. Nothing in a token that fails this check is believed, so the API stops here.

What happens to the token: It is sent to another API

The token’s claims

{
  "iss": "https://auth.example",
  "sub": "alice",
  "client_id": "calendar-assistant",
  "aud": "https://calendar.example",
  "scope": "calendar.read",
  "exp": 1794305400
}

What the API compares them with

this API:  mail.example
time now:  10:04
request:   GET /messages
The API’s five checks, in order
From an issuer I trust?YesThe mail API trusts auth.example too.
Signature intact?YesUnchanged since it was signed.
Addressed to me?Noaud is calendar.example. This is mail.example.
Still in date?SkippedNot reached.
Covers this request?SkippedNot reached.

Rejected. A token is addressed to one API, and every API has to read the address. If the mail API skipped this check, any service that was ever handed a token could turn around and use it somewhere else.

What happens to the token: It is used an hour later

The token’s claims

{
  "iss": "https://auth.example",
  "sub": "alice",
  "client_id": "calendar-assistant",
  "aud": "https://calendar.example",
  "scope": "calendar.read",
  "exp": 1794305400
}

What the API compares them with

this API:  calendar.example
time now:  11:04
request:   GET /events?day=tomorrow
The API’s five checks, in order
From an issuer I trust?Yesiss is auth.example.
Signature intact?YesUnchanged since it was signed.
Addressed to me?Yesaud is calendar.example.
Still in date?Noexp was 10:10. It is 11:04.
Covers this request?SkippedNot reached.

Rejected with 401. Access tokens are short-lived on purpose, often a few minutes. The agent can get a fresh one without sending Alice through sign-in again; the next lesson shows how.

What happens to the token: The agent tries to delete an event

The token’s claims

{
  "iss": "https://auth.example",
  "sub": "alice",
  "client_id": "calendar-assistant",
  "aud": "https://calendar.example",
  "scope": "calendar.read",
  "exp": 1794305400
}

What the API compares them with

this API:  calendar.example
time now:  10:04
request:   DELETE /events/42
The API’s five checks, in order
From an issuer I trust?Yesiss is auth.example.
Signature intact?YesUnchanged since it was signed.
Addressed to me?Yesaud is calendar.example.
Still in date?Yesexp is 10:10. It is 10:04.
Covers this request?NoDeleting needs calendar.write. Alice approved calendar.read.

Refused with 403. The token is genuine, and it does not cover this. It does not matter why the agent tried: a bug, a bad instruction, or something it read on a web page. The limit holds.

What happens to the token: Someone else sends a copy

The token’s claims

{
  "iss": "https://auth.example",
  "sub": "alice",
  "client_id": "calendar-assistant",
  "aud": "https://calendar.example",
  "scope": "calendar.read",
  "exp": 1794305400
}

What the API compares them with

this API:  calendar.example
time now:  10:04
request:   GET /events?day=tomorrow
The API’s five checks, in order
From an issuer I trust?Yesiss is auth.example.
Signature intact?YesUnchanged since it was signed.
Addressed to me?Yesaud is calendar.example.
Still in date?Yesexp is 10:10. It is 10:04.
Covers this request?Yesscope allows calendar.read.

All five pass, and the API answers. Nothing in this token says who is allowed to send it. A token like this is called a bearer token: it works for whoever holds it, like cash. A short life limits how long a stolen copy is useful. The final lesson shows how to make a copy worth far less.

All six at a glance
What happens to the tokenFrom an issuer I trust?Signature intact?Addressed to me?Still in date?Covers this request?
Nothing: sent as issuedYesYesYesYesYes
Someone edits itYesNoSkippedSkippedSkipped
It is sent to another APIYesYesNoSkippedSkipped
It is used an hour laterYesYesYesNoSkipped
The agent tries to delete an eventYesYesYesYesNo
Someone else sends a copyYesYesYesYesYes

Do not write the signature check yourself. Use a maintained library, and tell it in your own configuration which issuer, which audience, which keys and which signing method to accept. Never take those from the token: one that brings its own key, says where to fetch one, or says it needs no signature must be refused. Tell it which kind of token to accept as well. The same service signs other things, such as the ID token an app gets at sign-in, and a check that passes anything it signed would let one of those be used as an access token. A token in this format says what it is in its header: at+jwt, which may also be written application/at+jwt. The API must require one of the two. The last of the five, whether the token covers this request, is yours to write: only your code knows what each request needs. Decoding a token is not checking it.

When the API says no

The refusals in the figure are of two kinds, worth telling apart because they send you to different places.

  • 401 means the API did not accept the token at all. One of the first four checks failed, or no token arrived. If sign-in has just succeeded and the first call still fails, a common cause is the audience: the token was issued for a different address from the one the API knows itself by. Fix what the token is requested for, and never switch the audience check off to make the error go away. Check, too, that the token is being sent.
  • 403 means the token is good and does not cover this request. Retrying will not help. The agent needs a wider grant, and that means asking whoever can give one.

What a valid token does not prove

The last button is the uncomfortable one. A token records what was granted. It does not record who is allowed to carry it. A copy taken from a log file, a debugging session or an agent’s memory works exactly as well as the original, until it expires.

That is the trade a bearer token makes: simple to use, and only as safe as the places it passes through. It is why tokens are short-lived, why each is addressed to one API, and why it matters so much where an agent keeps them.

One more thing you will run into. Not every token can be read. Some are random strings that mean nothing by themselves, and the API asks the authorization server what each one stands for. Most of the checks then happen there, and comparing the scope with the request stays with the API. Asking is also how an API learns that a token was withdrawn early: one that only checks the token itself goes on accepting a withdrawn token until it expires.

Which kind a token is lies between the authorization server and the API, and they can change it at any time. So your agent sends a token on as it received it, and never depends on reading one.

Check yourself

Answer before you read on. Nothing is recorded.

1An API receives a token. The issuer is trusted, the signature is intact and it has not expired, but aud names a different API. What should it do?
  • Not quite. The address matters as much as the signature. An API that accepts tokens meant for other services lets anyone who receives a token reuse it here.

  • Not quite. There is no partial credit. A token for a different audience says nothing about what its holder may do here.

  • Right. A token is addressed to one API. Checking aud is what stops a token issued for one service from being replayed at another.

2Before sending the token, the agent changes calendar.read to calendar.write in the claims. What happens?
  • Right. The signature covers every claim. Change one character and it fails, and only the issuer holds the key that could sign the new version.

  • Not quite. The issuer signed the original claims, not the edited ones. The signature no longer matches.

  • Not quite. The authorization server is not involved at this point. The API detects the change itself, by checking the signature.

3Why do access tokens usually last minutes and not months?
  • Not quite. Signing keys are replaced far less often than that. The expiry is a choice the issuer makes for each token.

  • Right. A bearer token cannot tell its rightful holder from a thief. Expiry is the simplest limit on what a stolen copy is worth.

  • Not quite. The user is usually not asked again. With a refresh token the agent renews on its own, which the next lesson covers.

What to keep

  • A token is a statement signed by an authorization server: who it is about, which app it was given to, which API it is for, what it allows, and until when.
  • The API checks five things before believing any of it: issuer, signature, audience, expiry and scope.
  • Audience is the check that gets forgotten. A token is addressed to one API.
  • A valid token proves what was granted. It does not prove who is sending it: a plain token is a bearer token.

The names for these

The rest of this guide, and every specification, uses these terms. Each links to its entry in the glossary.

In this lessonIts nameDefined as
The service that checks who people are and issues tokensAuthorization serverThe system that authenticates users and clients, records consent, and issues tokens.
The token an API acceptsAccess tokenA credential an API accepts as proof that a request is authorized. Short-lived, addressed to one audience, and limited to particular scopes.
One line inside a tokenClaimOne named statement inside a token, such as sub (who it is about) or exp (when it expires). The issuer’s signature covers all of them.
Who signed itIssuerThe identifier of the system that issued a token. Verifiers check it against the issuers they trust.
Who it is aboutSubjectWho or what a token is about: the person an agent is acting for, or the agent itself when it acts alone.
Which API it is forAudienceThe recipient a token is intended for. A resource must reject tokens whose audience is someone else; this is the main defense against a token being replayed at a different service.
What it allowsScopeA name for a permission that a token carries, such as calendar.read. Scopes are coarse on purpose: they name kinds of access, not single actions.
Three chunks of text joined by dotsJWTA compact set of claims, usually signed and written as three chunks of text joined by dots. The common format for access tokens and ID tokens.
A token that works for whoever holds itBearer tokenA token that works for whoever presents it, like cash. A sender-constrained token is the kind that does not.

Go deeper

The same ground at full depth, when you want it. None of it is needed for the next lesson.