← Blog

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 correct header and four common mistakes. A server that checks the token rejects each of them

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/login and get an access_token back. 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.

A JWT reads without a key: the signature stops forgery but doesn't hide the contents
  • Anyone can "decode" a JWT. It's encoding, not encryption. So nothing secret goes in it, and the exp field 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#

BearerBasicAPI key
Looks likeAuthorization: Bearer <token>Authorization: Basic <user:password in Base64>A custom header such as X-Api-Key: <key>, or a URL parameter
What's insideA token the server issuedUsername and password, merely encodedA fixed key
LifetimeUsually shortUntil the password changesUntil it's revoked
Typical usesMost modern APIs, OAuth 2.0Internal services, older APIsPublic 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.

The Auth tab in Tetiva: Bearer as the default prefix, the token taken from an environment variable

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:

  1. Bearer is missing, or there's no space after it. The test API answers that with expected Authorization: Bearer <token>.
  2. Bearer appears 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 the bearer scheme, the Authorize dialog adds the word itself, so paste the bare token (more on Swagger UI in Swagger vs Postman).
  3. Stray characters: quotes around the token, or a bit of the neighbouring text copied along with it.
  4. The token has expired. Check exp if it's a JWT, or simply get a new one.
  5. 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).
  6. The header got lost on the way, for instance after a redirect to another host: curl with -L won't send Authorization to 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.