Chapter 03 of 11
Bearer Tokens and JWT
Opaque tokens against self-contained ones, what a JWT actually guarantees, the validation mistakes that keep recurring, and what RFC 8725 says about them.
Bearer tokens, and the JWT in particular, are the default of the modern API, because they separate the credential from the identity. The caller presents a token instead of a password, the token can be short-lived, and it can be issued by something other than the API that accepts it.
That separation is what OAuth 2.0 needs to work at all, and it comes with a property that needs naming plainly. A bearer token grants access to whoever holds it, so anyone who obtains one is the caller. There is no second factor and no proof of possession in the base scheme.
Opaque or self-contained
Two kinds of token travel the same way and behave very differently.
| Opaque token | Self-contained token, usually a JWT | |
|---|---|---|
| What it is | A random identifier | Signed claims the recipient can read |
| Validation | A lookup in the issuer's store | A signature check, with no lookup |
| Revocation | Immediate, because the store is authoritative | Hard, because nothing is consulted at validation time |
| Scaling | The store is on the hot path | Nothing shared is on the hot path |
| Leaks information | No, it says nothing on its own | Yes, since every claim is readable by whoever holds it |
The trade is revocation against a lookup. An opaque token can be withdrawn the moment you decide to, while a JWT stays valid until it expires. Adding machinery to revoke one reintroduces exactly the lookup you were avoiding.
The common resolution is short-lived JWTs plus a refresh token that is opaque and revocable. Access tokens live for minutes, so a leaked one has a narrow window, and the refresh token is checked against a store where it can be withdrawn.
What a JWT is made of
A JWT is three Base64url-encoded parts separated by dots, and each part has a distinct job.
eyJhbGciOiJSUzI1NiIsImtpZCI6IjJhIn0 header
.eyJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZSIsInN1YiI6InVzcl85MSIsImF1ZCI6ImFwaS5leGFtcGxlIiwiZXhwIjoxNzg5MDAwMDAwfQ payload
.MEUCIQDf... signatureThe header names the signing algorithm and usually a key identifier. The payload carries claims, and RFC 7519 registers the ones that matter for validation.
| Claim | Meaning | Why you validate it |
|---|---|---|
iss | Issuer | A token from another issuer is not yours to trust |
sub | Subject | Who the token is about |
aud | Audience | A token minted for another service must not work here |
exp | Expiry | The only thing limiting a leaked token |
nbf | Not before | Rejects a token presented too early |
iat | Issued at | Useful for age policies and for debugging |
jti | Token identifier | Lets you build a denylist for the tokens you must revoke |
Signing proves the token was not altered. It does not hide anything, because a signed JWT is not encrypted and every claim is readable by anyone holding the token. Encryption is a separate specification, so until you reach for it, put nothing in a JWT you would not put in a log line.
The validation mistakes that keep recurring
RFC 8725 exists because these repeat, and it is worth reading in full if you validate tokens yourself. Four are responsible for most incidents.
Trusting the algorithm in the header. The token tells you how it was signed, and a naive validator believes it. Historically that allowed alg: none, meaning a token with no signature at all. It also allows key confusion, where a token signed with HMAC using the public RSA key as the secret is accepted by a validator expecting RSA. The fix is to pin the algorithm you expect, per key, and reject anything else before verifying.
Not checking the audience. An identity provider issuing tokens for several services will happily give a caller a token for service A. If service B does not check aud, that token works there too.
Not checking the issuer. This is the same argument with a wider scope. A validator that accepts any correctly signed token will accept tokens from whichever issuer happens to share a key with yours.
Sloppy expiry handling. Not checking exp, or allowing an unbounded clock skew to accommodate a server whose clock was wrong once, turns expiry into decoration. A skew tolerance of a minute or two is reasonable, and an hour is not.
A fifth mistake is architectural, not a coding slip, and it deserves naming. Long-lived access tokens are convenient, and they remove your ability to respond to a compromise, so the lifetime belongs in minutes and not in days.
Rotating signing keys
A signing key has to be replaceable at some point, and doing that without downtime needs two things in place beforehand.
The header's key identifier says which key signed this token, so a validator can hold several. Publishing your public keys at a well-known location lets validators fetch them and pick up a new one without a deployment.
The rotation then works in the usual overlapping shape. Publish the new key, start signing with it, keep accepting the old one until every token signed with it has expired, and then withdraw it.
Refresh tokens
An access token measured in minutes needs something to renew it, and that is the refresh token. Three properties deserve insisting on.
Keep it opaque and stored. The point of a refresh token is that you can revoke it, which needs a lookup.
Rotate on use. Issue a new refresh token every time one is redeemed, and invalidate the old one. RFC 9700 recommends this for public clients.
Detect reuse. If a refresh token is presented twice, one of the two presenters is an attacker and you cannot tell which. Invalidating the whole chain is the safe response.
In Routebase
Two entries in the catalogue cover this ground, and they differ in who mints the token.

Bearer Token sends a token you already have in the Authorization header. The value accepts a {{VARIABLE}} placeholder, so the token lives in a secret variable, and a token minted by a pipeline can be injected at run time instead of being stored at all.
JWT Bearer generates a signed token for the request. You pick the algorithm from HS256, HS384, HS512, RS256, RS384 and RS512, supply either a shared secret or a private key, and write the payload as JSON. A live preview shows the token payload as you edit it, which catches a malformed claim before a suite depends on it. Custom header parameters are available for the cases that need a key identifier or a type, and the token can be placed in a header or a query parameter.
That combination covers both sides of a JWT integration. A suite testing an API that accepts JWTs mints its own token with the right claims. A negative case such as an expired token or a wrong audience is then a matter of editing the payload, and nobody has to issue you a bad token.
Every field accepts variable placeholders, so the signing secret or private key sits in a secret variable and never in the configuration. See Project Auth for the editor, and testing authentication for the negative cases worth writing.
Frequently asked questions
What is a bearer token?
A bearer token is a string that grants access to whoever presents it, which is what the word bearer means. RFC 6750 defines how it travels, namely in the Authorization header with the Bearer scheme. The token itself may be an opaque identifier the server looks up or a self-contained token such as a JWT, and the transport is identical either way.
What is a JWT?
A JSON Web Token, defined in RFC 7519, is a compact token carrying claims as JSON, signed so a recipient can verify it was not altered. It has three Base64url parts, which are a header naming the algorithm, a payload of claims, and a signature. Because the claims travel with the token, a server can validate it without a database lookup, and that property is both its main advantage and the source of most of its problems.
Is a JWT encrypted?
A signed JWT is not encrypted, so anyone holding it can read every claim by decoding the payload. Signing proves the token was not modified and says nothing about confidentiality. Encryption is a separate specification called JWE, so until you use it, never put anything in a JWT that you would not put in a log line.
What are the most common JWT validation mistakes?
Trusting the algorithm named in the token header, which allows the none algorithm and key confusion attacks. Not checking the issuer and the audience, so a token minted for a different service is accepted. Not checking expiry, or accepting an unbounded clock skew. RFC 8725 collects these as Best Current Practice, and the short version is to pin the expected algorithm and validate every registered claim you rely on.
Last reviewed by The Routebase Team.