# Bearer Tokens and JWT — Routebase

> Opaque tokens against self-contained ones, what a JWT actually guarantees, the validation mistakes that keep recurring, and what RFC 8725 says about them.

Canonical page: https://routebase.dev/guides/api-authentication/bearer-tokens-and-jwt/
Chapter 3 of 11 · API Authentication · Last reviewed 2026-09-12 · The Routebase Team

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.

```txt
eyJhbGciOiJSUzI1NiIsImtpZCI6IjJhIn0      header
.eyJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZSIsInN1YiI6InVzcl85MSIsImF1ZCI6ImFwaS5leGFtcGxlIiwiZXhwIjoxNzg5MDAwMDAwfQ   payload
.MEUCIQDf...                              signature
```

The 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.

_Figure: Every gate a token owes the receiver, with the four this section walks through marked, because each of them lets the wrong token in._

**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.

_Screenshot: The preview decodes the token as you edit it, so a malformed claim shows up before a suite depends on it._

**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](https://docs.routebase.dev/project-auth/) for the editor, and [testing authentication](/guides/api-authentication/api-authentication-testing/) 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.

---

[Routebase](https://routebase.dev/) — [Sign up](https://app.routebase.dev/): Every account starts with a 14-day Pro trial — no credit card required.
