Bearer tokens: what they are and how to send one
Short answer: a Bearer token is an access string the server issues to you: at sign-in, through OAuth 2.0, or in a service's account settings. You send it with every request in the Authorization header, after the word Bearer and one space:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
"Bearer" means whoever holds it. The server lets in anyone who presents the token and doesn't ask who they are, like a cinema ticket: the usher checks the ticket, not your passport. That's why a Bearer token deserves the same care as a password.
The scheme is defined in RFC 6750, part of OAuth 2.0, but it's used far beyond OAuth.
How to send a Bearer token in the Authorization header#
The header has three parts: the name Authorization, the word Bearer, a single space, then the token. Nothing else: no quotes, no colon after Bearer.
The standard treats the scheme name as case-insensitive, so bearer works too. Some servers compare it literally, though, so writing it with a capital B is safer.
In curl it looks like this (the flags are explained in the curl post):
curl https://playground.tetiva.app/bearer \
-H "Authorization: Bearer abc123"
That URL is our test API. It accepts any token and echoes back what it received.
In code the header is set like any other. With fetch:
fetch("https://playground.tetiva.app/bearer", {
headers: { Authorization: `Bearer ${token}` },
});
With axios:
axios.get("https://playground.tetiva.app/bearer", {
headers: { Authorization: `Bearer ${token}` },
});
In Python with requests:
requests.get("https://playground.tetiva.app/bearer",
headers={"Authorization": f"Bearer {token}"})
How to get a Bearer token#
The server issues the token, and how you get one depends on the service. The three usual routes:
- Signing in with a username and password. You post them to something like
/auth/loginand get anaccess_tokenback. From then on it goes into every request. - OAuth 2.0. The app gets a token from an authorization server, for example after a "Sign in with…" button, or with a client ID and client secret when a service rather than a person makes the calls.
- A personal token from settings. Many services, GitHub and GitLab among them, let you issue a token in your account settings. Such tokens are long-lived and can be revoked.
A token you get at sign-in is usually short-lived, from a few minutes to a day. It often comes with a refresh_token that gets you a new access token without typing the password again.
In the server's response it usually looks like this:
{
"access_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "8xLOxBtZp8..."
}
token_type is what tells you to send it as Bearer. That's why an access token is so often just called a Bearer token.
Is a Bearer token a JWT?#
Bearer is a way to send a token; JWT is a format for the token itself. They get mixed up because JWTs are so often sent as Bearer tokens, but a Bearer token can just as well be a random string.
An opaque token looks like 9f2c4e7a1b.... You can't learn anything from it; the server checks it against its own records.
A JWT is three parts separated by dots: a header, the data, and a signature. The first two are Base64URL-encoded and readable by anyone, no key required.
- Anyone can "decode" a JWT. It's encoding, not encryption. So nothing secret goes in it, and the
expfield tells you at a glance whether the token has expired. - Don't paste a live token into an online decoder. It's a working access key, and the site you pasted it into can use it. Practise on a token from a test environment, or an expired one.
Bearer token vs Basic auth vs API key#
| Bearer | Basic | API key | |
|---|---|---|---|
| Looks like | Authorization: Bearer <token> | Authorization: Basic <user:password in Base64> | A custom header such as X-Api-Key: <key>, or a URL parameter |
| What's inside | A token the server issued | Username and password, merely encoded | A fixed key |
| Lifetime | Usually short | Until the password changes | Until it's revoked |
| Typical uses | Most modern APIs, OAuth 2.0 | Internal services, older APIs | Public APIs, service to service |
None of the three encrypts the request by itself; that's HTTPS's job, so send tokens over https:// only.
The standard also allows sending the token as an access_token URL parameter but advises against it: URLs end up in server logs and browser history. DPoP (RFC 9449) goes further and binds the token to a key only the client holds, so a stolen token alone isn't enough. Most APIs still use plain Bearer.
Adding a Bearer token in Postman and other API clients#
You don't have to type the Authorization header by hand. Postman, Insomnia, Tetiva and other clients have a Bearer Token type on the Auth tab: paste the token and the client builds the header.
In Tetiva that type has two fields, Prefix and Token. The prefix defaults to Bearer; some APIs expect another word, such as Token, and you can change it. With an empty token field the header isn't sent at all. A trailing newline in a pasted token is trimmed automatically, so copying a token from a terminal is safe.
The token is easier to manage in an environment variable, {{token}}, than inside each request. You change it in one place and can keep separate tokens for staging and production. You can also set auth once on the collection; requests set to Inherit use it. Details are in the authentication docs.
If the token comes back from a login request, a post-response script can store it:
pm.environment.set("token", pm.response.json().access_token);
If the token comes from OAuth 2.0, skip the script: pick the OAuth 2.0 type, press Get token, and Tetiva refreshes an expiring token before the request goes out (details).
Bearer token returns 401: what to check#
Look at the response first: many servers explain in the WWW-Authenticate header, for example error="invalid_token", or in the body. If they don't, work down this list:
Beareris missing, or there's no space after it. The test API answers that withexpected Authorization: Bearer <token>.Bearerappears twice. The token was pasted together with the word into a field that adds it already. Swagger UI is the classic case: when the spec uses thebearerscheme, the Authorize dialog adds the word itself, so paste the bare token (more on Swagger UI in Swagger vs Postman).- Stray characters: quotes around the token, or a bit of the neighbouring text copied along with it.
- The token has expired. Check
expif it's a JWT, or simply get a new one. - The token is from another environment. A staging token won't work on production. If you keep tokens in environments, check which one is selected (there's a Friday story about prod on exactly that).
- The header got lost on the way, for instance after a redirect to another host: curl with
-Lwon't sendAuthorizationto a different host unless you add--location-trusted.
If the server answers 403 instead, the token is fine: it works, it just isn't allowed to do this. The difference between 401 and 403 is covered in the status codes post.
Try it on the test API#
Send the request without the header, without the word Bearer, and with a correct header:
# 401: no header
curl https://playground.tetiva.app/bearer
# 401: token without the word Bearer
curl https://playground.tetiva.app/bearer -H "Authorization: abc123"
# 200: all correct
curl https://playground.tetiva.app/bearer -H "Authorization: Bearer abc123"
A token in a chat, a screenshot or your shell history works for anyone who sees it. If one has already leaked, the token in the screenshot walks through what to do, step by step.