Bearer token

A bearer token is a credential whose only security property is possession: hold it, and the API treats you as the client it was issued to. This guide covers the exact wire format, what may legitimately be inside the token, how the scheme differs from Basic authentication, the failure responses it defines, and the handling rules that keep a token from leaking. Every example here can be sent from the online API tester as you read.

What “bearer” actually means

The Bearer scheme was specified in RFC 6750, The OAuth 2.0 Authorization Framework: Bearer Token Usage, as the companion to the OAuth 2.0 framework in RFC 6749. It is registered in the IANA HTTP Authentication Scheme Registry alongside Basic (RFC 7617) and Digest, so it is a first-class HTTP authentication scheme rather than a vendor convention — even though a great many APIs now use it with no OAuth authorisation server anywhere in sight.

The word bearer is doing all the work, and it is borrowed from finance: a bearer bond pays whoever physically holds it, with no name attached. A bearer token behaves the same way. There is no proof-of-possession step, no signature computed over the request, and no binding to the caller's key, device or address. The server does not verify that you are the party the token was issued to; it verifies that the token is genuine and unexpired, and that is the whole check. Whoever holds the token is the client, permanently, until the token expires or is revoked.

Three operational rules fall straight out of that definition, and they are not negotiable:

  • HTTPS only. A bearer token sent over plaintext HTTP is a password shouted across a room. RFC 6750 requires TLS; pair it with Strict-Transport-Security so a client cannot be downgraded.
  • Short lifetimes. Because there is no way to prove a holder is legitimate, the only practical containment for a stolen token is that it stops working quickly.
  • Never in a URL, never in a log. Anything that records the request line or the Authorization header records a working credential.

If those constraints are unacceptable for your threat model, the alternative is a sender-constrained token — covered at the end of this guide — which cryptographically binds the token to the client so that possession alone is no longer enough.

Anatomy of the header

The wire format is one line, and it is unforgiving:

GET /v1/orders?limit=25 HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjRmM2EifQ.eyJzdWIiOiJ1c2VyXzQyIn0.Xk1sJ3

Read it left to right. Authorization is the field name, defined by RFC 9110 as carrying credentials for the target resource. Bearer is the authentication scheme token — technically case-insensitive, as all scheme names are, but conventionally capitalised, and a small number of strict servers do compare it literally, so write Bearer. Then exactly one space, then the token. RFC 6750 gives the credential a grammar called b64token, which permits the base64, base64url and token characters — letters, digits, -, ., _, ~, +, / and trailing = padding. Notably it does not permit spaces, quotes or newlines.

Almost every “my token is rejected but it looks fine” report traces to one of these:

MistakeWhat gets sentResult
Scheme omittedAuthorization: eyJhbGciOi…Server sees an unknown scheme; usually 400 or 401
Doubled schemeAuthorization: Bearer Bearer eyJ…Token parsed as the literal string “Bearer”; invalid_token
Token quotedAuthorization: Bearer "eyJ…"Quotes are outside the b64token grammar; rejected or decoded to nonsense
Trailing newline from a copy-pasteBearer eyJ…\nSilently invalid signature, or a client-library header-injection error
Two spaces after the schemeBearer  eyJ…Lenient servers cope, strict ones do not
Wrong field nameAuthentication: Bearer eyJ…Header ignored entirely; looks like no credential was sent
Template not interpolatedBearer ${TOKEN}The literal placeholder reaches the server — surprisingly common in shell scripts

If you are not sure which of these you are hitting, send the request from the API tester and look at the exact bytes it reports sending — an extra space or a stray newline is visible there in a way it never is inside application code.

Field names are case-insensitive, so authorization: is fine — HTTP/2 and HTTP/3 require lowercase on the wire anyway. See the HTTP headers guide for the wider rules on header syntax and which fields survive a proxy.

What is inside the token: JWT or opaque

This is the single most common misconception about the scheme. A bearer token is not necessarily a JWT. “Bearer” describes how the credential travels; the format of the credential is an entirely separate decision made by whoever issues it. There are two mainstream choices.

A JWT (RFC 7519) is self-contained: three base64url segments carrying a header, a claims payload and a signature. A resource server holding the right key can validate it without calling anybody, and can read the claims — subject, scope, expiry — directly out of it. An opaque token is just a long random string with no internal structure at all; it means nothing except as a lookup key into the issuer's database or cache, where the real session record lives.

PropertyJWT bearer tokenOpaque bearer token
Size on the wireHundreds of bytes to a few KB; grows with every claimTypically 20–60 characters, constant
VerificationOffline — signature check against a public key or shared secretRequires a lookup at the issuer or a shared store
RevocationHard. A valid signature and a future exp means accepted, unless you add a denylistInstant — delete the row and the next request fails
Information leakageThe payload is encoded, not encrypted; anyone who intercepts it can read every claimReveals nothing; the string is meaningless without the store
IntrospectionOptional; the claims are already thereThe point of RFC 7662 Token Introspection — POST the token to /introspect and get back active, scope, exp, sub
Best fitMany resource servers, high request volume, tolerable staleness windowSensitive operations, immediate logout, a single trusted issuer

Neither wins outright, and the trade-off — statelessness against revocability — is examined in depth in the OAuth vs JWT guide, which also explains how tokens get issued in the first place. For the practical side, if you hold a token and want to know which kind you have, paste it into the JWT decoder: if it splits into three dot-separated parts and produces readable JSON, it is a JWT and the decoder will show you every claim, the algorithm and the expiry. If it does not, it is opaque, and the only party who can tell you anything about it is the issuer.

Decoding is not verifying. A decoder reads the payload without checking the signature — which is exactly why you must never trust claims you have not cryptographically validated. The JWT decoder page covers algorithm confusion and claim validation in detail.

Bearer vs Basic vs API key

Bearer is one of three patterns you will meet on real APIs, and the differences are substantive rather than cosmetic.

Basic authentication

RFC 7617 Basic sends Authorization: Basic base64(username:password) on every single request. The critical point, which trips up a remarkable number of teams: base64 is an encoding, not encryption. It is trivially and losslessly reversible by anyone, with no key involved. You can prove it to yourself in ten seconds — take YWRtaW46aHVudGVyMg==, paste it into the Base64 decoder, and read the password back out. Anything that captures the header — a log aggregator, a proxy, a crash report — has captured the real password.

That is the structural problem with Basic: the credential it transmits is the user's long-lived password. It cannot be scoped down to “read orders only”, it cannot be expired independently of the account, revoking it means a password reset for a human being, and it is fundamentally incompatible with multi-factor authentication, because a second factor is an interactive step and Basic has no interaction in it.

Bearer

Bearer transmits a credential that is derived from an authentication event rather than being the authentication secret itself. That indirection is the entire benefit: the token can be minted with a narrow scope, given a short expiry, revoked without touching the account, rotated on a schedule, and issued only after whatever multi-factor ceremony the identity provider requires.

BearerBasicAPI key header
What is transmittedAn issued access tokenThe user's actual password, base64-encodedA long-lived shared secret
Reversible to a password?NoYes, triviallyNo, but it is itself the secret
Typical lifetimeMinutes to an hourUntil the user changes the passwordMonths or years
ScopingPer-token scopes and audienceNone — full account rightsSometimes, per key
RevocationRevoke one token; others survivePassword reset, affecting everythingDelete the key
RotationAutomatic, via refreshManual and disruptiveManual, usually with an overlap window
Works with 2FAYes — factors happen before issuanceNoNot applicable; identifies a machine
HeaderAuthorization: Bearer …Authorization: Basic …X-API-Key: … or similar

The API key in a custom header

The third pattern — X-API-Key: sk_live_…, or an equivalent vendor-named field — is not an HTTP authentication scheme at all. It is a convention, which means no standard challenge on failure, no scheme registry entry, and no interoperable tooling. It identifies an application rather than a user acting through an application, and it is normally long-lived, so it lives in a secrets manager, not in a browser. Note also that the X- prefix itself was deprecated by RFC 6648 back in 2012, so a new API is better off naming the field without it. Where an API key genuinely represents a machine identity with stable permissions, it is a perfectly reasonable choice; where it is used as a stand-in for user sessions, it is a bearer token with none of the lifetime discipline.

For the wider landscape — sessions, mTLS, HMAC request signing and where each belongs — see the API authentication overview.

The other transmission methods, and why not to use them

RFC 6750 actually defines three ways to send a bearer token, and it is worth knowing all three so you can recognise them in someone else's API.

  1. Authorization request header field. Authorization: Bearer <token>. This is the method the spec prefers and the only one you should implement.
  2. Form-encoded body parameter. A parameter named access_token inside a request body with Content-Type: application/x-www-form-urlencoded. Permitted only when the request is not a GET, the body is single-part, and the parameter is sent with UTF-8 encoding. It exists for constrained clients that genuinely cannot set headers.
  3. URI query parameter. GET /v1/orders?access_token=<token>. The specification includes it and then immediately warns against it, and later OAuth security guidance goes further and says not to use it.

The query-parameter method is discouraged for a very concrete reason: URLs are recorded everywhere. They land in web-server access logs, in reverse-proxy and CDN logs, in browser history, in the Referer header sent to any third-party resource the page loads, in analytics pipelines, in error trackers, and in the bookmark someone pastes into a group chat. Each of those is a full copy of a working credential sitting in a system that was never designed to hold secrets. A response served from such a URL is also more likely to be cached by an intermediary that has no idea it is authorised content. RFC 6750 additionally requires that any response to a query-parameter-authenticated request carries Cache-Control: private.

The rule is short: use the header. If you are forced to consume an API that only accepts a query parameter, treat the token as compromised-by-design — keep it extremely short-lived and narrowly scoped.

Failure responses: 401, 403 and the challenge

A resource server that rejects a bearer token has a defined vocabulary for saying so, and using it correctly is what lets generic client libraries recover automatically.

401 Unauthorized

401 means the credential is missing, malformed, expired or otherwise not accepted. RFC 9110 requires a WWW-Authenticate header on every 401 — a 401 without a challenge is a spec violation, and it is depressingly common. For Bearer, the challenge looks like this:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api.example.com",
                  error="invalid_token",
                  error_description="The access token expired"
Content-Type: application/json

RFC 6750 defines three error codes for that error parameter:

Error codeMeaningStatus
invalid_requestThe request is malformed — a duplicated parameter, a token sent two different ways, a missing parameter400
invalid_tokenThe token is expired, revoked, malformed or fails validation401
insufficient_scopeThe token is genuine but does not carry the scope this request needs403

Note the distinction the spec draws, because it is the one people get backwards. 401 is about the credential; 403 is about the permission. If the token cannot be validated, answer 401 and the client knows to refresh or re-authenticate. If the token validates perfectly but lacks orders:write, answer 403 with error="insufficient_scope" and a scope parameter naming what was required — retrying with the same token is pointless, and telling the client so saves it a wasted refresh round-trip. Keep error_description human-readable and non-sensitive: it is a debugging aid, not a place to explain your validation internals to an attacker.

Symptom to cause

SymptomLikely cause
401 on every request, including ones that worked yesterdaySigning key rotated, or the token was issued for a different environment; check iss and the kid in the header
Works in curl, fails in the browserNot authentication at all — the preflight is rejecting the Authorization header. See the CORS guide
Works for a while, then 401 mid-sessionAccess token hit its exp; the client needs a refresh path, not a longer token
401 immediately after issuanceClock skew — the resource server's clock is behind the issuer's, so nbf or iat is in its future. Allow a small leeway, typically 30–60 seconds
403 on one endpoint onlyScope missing from the token; re-request authorisation with the scope included
Token “looks right” but signature failsWhitespace, a truncated copy-paste, or the wrong audience. Decode it in the JWT decoder and compare aud
Header appears absent server-sideA proxy or load balancer stripped Authorization, which some strip by default on redirects and cross-host rewrites

Lifetime, refresh and rotation

Because bearer semantics offer no way to tell a thief from the rightful holder, lifetime is the security control. Hence the standard two-token split.

The access token is the bearer token you attach to API calls. It is deliberately short-lived — five minutes to an hour is typical — so a leaked one has a small blast radius. The refresh token lives much longer, but it is never sent to a resource server: its only destination is the authorisation server's token endpoint, where it is exchanged for a fresh access token. That asymmetry is the point. The credential that travels constantly, across many services and log pipelines, expires fast; the credential that lasts is exposed to exactly one endpoint over one TLS connection.

For public clients — anything whose code the user can read, which means every SPA and mobile app — refresh token rotation is now the expected practice. Each refresh returns a new refresh token and invalidates the previous one. If an old refresh token is ever presented again, that is reuse detection: either the token was stolen and the thief got there first, or the legitimate client is replaying, and the server cannot distinguish the two. The correct response is to invalidate the whole token family and force re-authentication. It is disruptive by design, and it converts a silent long-lived compromise into a single visible logout.

Practical client rules: refresh proactively, a little before exp, rather than waiting for a 401; serialise refreshes so that ten concurrent requests hitting expiry do not fire ten refreshes and trip your own reuse detection; and retry the original request exactly once after a successful refresh, then give up. How tokens are issued in the first place — the grant flows, the token endpoint, PKCE — belongs to the OAuth vs JWT guide.

Storing the token in a client

On a server or a native app this is a solved problem: keep the token in memory, or in the OS keychain or credential store, and never write it to a config file that might reach version control. In a browser there is a genuine trade-off, and anyone who tells you it has one obviously correct answer is skipping a step.

localStorage or sessionStorage is readable by any JavaScript running on the page. That means a single cross-site scripting flaw — in your own code, or in any dependency in your bundle, or in a tag-manager script someone added last quarter — exfiltrates the token in one line. The upside is that it is simple, it works cleanly across origins, and it is immune to CSRF because nothing is attached automatically.

An httpOnly cookie inverts both properties. JavaScript cannot read it, so XSS cannot steal the token directly — though a script running on your page can still use the session by issuing requests, which is a smaller but non-zero exposure. The cost is that the browser attaches the cookie automatically to qualifying requests, which is precisely the condition cross-site request forgery exploits. You answer that with SameSite=Lax or Strict, the Secure flag, and an anti-CSRF token for state-changing requests. The CORS guide is the place to understand why cookies and the Authorization header behave so differently cross-origin.

A common middle path for SPAs: keep the access token in a JavaScript variable in memory only — gone on refresh, never persisted — and keep the refresh token in an httpOnly, Secure, SameSite cookie scoped to the token endpoint's path. XSS cannot read either one persistently, and the cookie's automatic attachment is harmless because the token endpoint is not a state-changing API. It is more work, and it is the honest answer to a problem with no free option.

Security checklist

  • TLS everywhere. Reject plaintext outright, and send Strict-Transport-Security with a long max-age so a client is never downgraded.
  • Short exp. Minutes, not days. Pair with refresh rather than stretching the access token.
  • Validate aud and iss. A token minted for a different service by the same issuer must not be accepted by yours. Checking only the signature is how a low-value service's token becomes a high-value service's session.
  • Check scope per endpoint, not once at the gateway. Answer 403 with insufficient_scope when it is missing.
  • Never log the Authorization header. Add it to the redaction list in your logger, your APM agent, your error reporter and your HTTP debug dumps — all four, because they are separate code paths and each has leaked tokens for somebody.
  • Redact in support tooling. Session replay and “copy as cURL” buttons both capture headers by default.
  • Rotate signing keys on a schedule, publish them at a JWKS endpoint, and key them by kid so old and new coexist during the rollover.
  • Have a revocation story before you need one: an introspection endpoint, a short-lived denylist keyed by jti, or a lifetime short enough that revocation is moot.
  • Do not put secrets in a JWT payload. It is encoded, not encrypted — see the JWT decoder for a live demonstration.

Beyond bearer: sender-constrained tokens

If “possession is sufficient” is too weak for what you are protecting, the answer is a token bound to something the client can prove it controls. Two standards do this. Mutual-TLS client certificate-bound access tokens (RFC 8705) tie the token to the client's TLS certificate: the token records a thumbprint of the certificate, and a resource server accepts it only on a connection presenting that same certificate. DPoP (RFC 9449) — Demonstrating Proof of Possession — works at the application layer instead: the client holds a key pair and sends a signed JWT proof in a DPoP header alongside each request, covering the HTTP method and URI, and the access token is bound to the public key. A stolen DPoP-bound token is useless without the private key. Both cost more to operate than plain Bearer, which is why plain Bearer remains the default for most APIs — but for financial and healthcare interfaces, sender-constraining is increasingly the requirement.

Try it yourself

The fastest way to internalise this is to send a few requests. Open the API tester, pick the Auth tab, choose Bearer and paste a token — the tool builds Authorization: Bearer <token> for you, with the one space in the right place and nothing appended, which quietly eliminates half the mistakes in the table above. Then send the same request without the token and read the WWW-Authenticate header the server returns; that challenge tells you exactly what it wanted.

A useful three-step exercise: call a token-protected endpoint from the REST API tester and confirm a 200; deliberately corrupt one character of the token and watch it become a 401 with error="invalid_token"; then call an endpoint your scopes do not cover and compare the 403. Use “copy as cURL” to lift the whole request into a terminal or a teammate's message — and remember that the copied command contains a live credential, so treat it the way you would treat a password before pasting it anywhere.

If a token is being rejected and you cannot see why, decode it in the JWT decoder and check exp, aud and iss before assuming the server is at fault. For the format-versus-framework question underneath all of this, read OAuth vs JWT; for the other schemes you might use instead, the API authentication guide surveys the field; and for building a repeatable set of checks around an authenticated endpoint, the REST API testing guide covers the workflow.

Frequently asked questions

What does Bearer mean in the Authorization header?

That possession of the token is the whole proof. Nothing is signed over the request and nothing binds the token to you, so whoever holds it can use it exactly as the real client would — hence HTTPS only, short lifetimes, and never in URLs or logs.

Is a bearer token always a JWT?

No. Bearer describes transmission, not format. A bearer token may be a JWT, verifiable offline, or an opaque random string resolved by a lookup. GitHub and many others issue opaque bearer tokens.

What is the correct format of a bearer token header?

Authorization: Bearer, one space, then the token — no quotes, no trailing newline. The scheme name is case-insensitive but conventionally written Bearer, and the token must match RFC 6750's b64token grammar.

Should a missing scope return 401 or 403?

401 when the token is missing, malformed or expired, with a WWW-Authenticate: Bearer challenge. 403 with insufficient_scope when the token is valid but lacks the required permission.

Can I send an access token in the URL query string?

RFC 6750 defines the method but discourages it. URLs reach access logs, browser history, Referer headers and analytics, so the token leaks in several places at once. Use the header.

Where should a browser app store a bearer token?

There is no free answer. localStorage is readable by any XSS on the page; httpOnly cookies are not, but they are attached automatically and bring CSRF considerations you must answer with SameSite and anti-CSRF tokens.