By AndyPublished

JWT vs Bearer Token: Is a JWT a Bearer Token?

“Bearer token” and “JWT” answer different questions. A bearer token is any token that grants access to whoever presents it, with no further proof of identity; the term comes from OAuth 2.0 (RFC 6750), and the token is usually sent as Authorization: Bearer <token>. A JWT is a token format: signed JSON claims (RFC 7519). Bearer describes the usage, JWT describes the contents.

They overlap heavily because most JWT access tokens are used as bearer tokens. But a bearer token does not have to be a JWT (it can be an opaque random string), and a JWT does not have to be a bearer token (with DPoP it is bound to a key the client must prove it holds).

Is a JWT a Bearer Token?

Usually, yes. When an OAuth server returns "token_type": "Bearer" alongside a JWT access token, that JWT is a bearer token: the API accepts it from anyone who sends it, as long as the signature and claims check out. The signature proves the token came from the issuer. It says nothing about whether the party presenting it is the one it was issued to.

RFC 6750 puts it plainly: any party in possession of a bearer token can use it to access the associated resources, without demonstrating possession of a cryptographic key. That is why a stolen JWT works exactly as well for the thief as for the legitimate client until its exp passes.

Are JWT and Bearer Token the Same?

No. They sit on different axes, and each can exist without the other:

AspectBearer tokenJWT
What the term describesHow a token is used: possession alone grants accessWhat a token looks like: signed JSON claims
SpecificationRFC 6750 (OAuth 2.0 Bearer Token Usage)RFC 7519, signed with JWS (RFC 7515)
FormatAny string: opaque, JWT, or vendor-specificThree Base64url segments: header.payload.signature
Sent asAuthorization: Bearer <token>Anywhere: Bearer header, DPoP header, cookie, form field
Validated byWhatever the format requires: lookup, introspection or signatureSignature plus iss, aud, exp checks
Opposite conceptSender-constrained tokens (DPoP, mutual TLS)Opaque (reference) tokens
  • ·Bearer, not JWT: an opaque access token like tGzv3JOkF0XG5Qx2TlKWIA, validated by introspection. Also API keys sent in a Bearer header.
  • ·JWT, not bearer: a DPoP-bound access token, an OpenID Connect ID token (consumed by the client, never presented to an API), or a JWT used as a client assertion.
  • ·Both: the typical JWT access token from Auth0, Okta, Entra ID or Cognito.

The format question, JWT versus opaque, is covered in JWT vs opaque tokens. To see whether the token you are sending is a JWT at all, paste it into the jwtdecode.app decoder; it decodes locally, and an opaque token simply will not parse.

JWT Bearer Authentication: How It Works

“JWT bearer authentication” means an API that reads a JWT from the Bearer scheme of the Authorization header and validates it. The request looks like this:

GET /orders HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6IjIwMjYtMDkifQ.eyJpc3Mi...

RFC 6750 also allows the token in a form-encoded body field or an access_token query parameter, but the query form ends up in logs and browser history and should not be used. Clients must send the token in exactly one place per request.

A Bearer middleware for Express

A minimal Express 5 middleware using jose that validates a JWT bearer token and returns the RFC 6750 challenges:

import { createRemoteJWKSet, jwtVerify, errors } from 'jose';

const JWKS = createRemoteJWKSet(new URL('https://auth.example.com/.well-known/jwks.json'));

export function requireBearer(requiredScope) {
  return async (req, res, next) => {
    const [scheme, token] = (req.headers.authorization ?? '').split(' ');
    if (scheme?.toLowerCase() !== 'bearer' || !token) {
      res.set('WWW-Authenticate', 'Bearer realm="api"');
      return res.status(401).end();
    }
    try {
      const { payload } = await jwtVerify(token, JWKS, {
        issuer: 'https://auth.example.com/',
        audience: 'https://api.example.com',
        algorithms: ['RS256'],
      });
      const scopes = String(payload.scope ?? '').split(' ');
      if (requiredScope && !scopes.includes(requiredScope)) {
        res.set('WWW-Authenticate', `Bearer error="insufficient_scope", scope="${requiredScope}"`);
        return res.status(403).end();
      }
      req.auth = payload;
      next();
    } catch (err) {
      const description = err instanceof errors.JWTExpired ? 'The access token expired' : 'The access token is invalid';
      res.set('WWW-Authenticate', `Bearer error="invalid_token", error_description="${description}"`);
      res.status(401).end();
    }
  };
}

// app.get('/orders', requireBearer('orders:read'), handler);

The authentication scheme name is case-insensitive, hence the toLowerCase(). Frameworks ship this as a package: ASP.NET Core’s Microsoft.AspNetCore.Authentication.JwtBearer with AddJwtBearer(), Spring Security’s OAuth 2.0 resource server, and similar. The checks in the middleware are explained step by step in the JWT verification guide.

Bearer error responses

errorStatusMeaning
(no error code)401No credentials were sent. Respond with a bare WWW-Authenticate: Bearer challenge.
invalid_request400Malformed request: missing parameter, token sent in more than one place.
invalid_token401Expired, revoked, malformed or failed signature. The client should get a new token.
insufficient_scope403Token is valid but lacks the scope for this resource.

Keep error_description generic. It is for developers, and it should not reveal which check failed in detail to an unauthenticated caller. When you are on the receiving end of these errors, how to debug a JWT walks through the causes in order.

Sending a JWT Bearer Token From a Client

On the client side, a bearer token is just a header. With curl:

TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCJ9..."
curl -i https://api.example.com/orders -H "Authorization: Bearer $TOKEN"

And with fetch in the browser or Node.js:

const res = await fetch('https://api.example.com/orders', {
  headers: { Authorization: `Bearer ${accessToken}` },
});
if (res.status === 401) {
  // Read res.headers.get('WWW-Authenticate'): invalid_token means refresh and retry once.
}

The most common client-side mistakes are small: a missing space after Bearer, quotes copied around the token, the word Bearer included twice, or the ID token sent instead of the access token. Testing tools add their own twists, such as Postman’s Bearer Token auth type adding the prefix for you; see JWTs with curl and Postman. Browsers never attach a bearer token automatically, unlike a cookie, which is why header-based bearer tokens are not exposed to CSRF in the way cookie sessions are, and also why your client code is responsible for attaching and refreshing them on every call.

JWT Bearer Token Risks and Mitigations

Because possession equals access, the defences for bearer JWTs are about limiting exposure and lifetime:

  • ·Short lifetimes: 5 to 15 minutes for access tokens, renewed with a refresh token. See the refresh token pattern.
  • ·Narrow audience and scope: a token for one API should be rejected by every other API.
  • ·Careful storage: in browsers, keep tokens out of places injected scripts can read where possible. The options are compared in localStorage vs cookies.
  • ·No logging: strip the Authorization header from access logs, error trackers and proxies.
  • ·HTTPS only: RFC 6750 requires TLS for every request that carries a bearer token.

The JWT Bearer Grant Type (RFC 7523)

A related but different use of the word: RFC 7523 defines how a JWT can be used to obtain an access token, rather than being the access token. The client signs a short-lived JWT assertion with its private key and posts it to the token endpoint:

POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&assertion=eyJhbGciOiJSUzI1NiIsImtpZCI6InNhLTEifQ.eyJpc3MiOiJzdmMtYWNjdEBleGFtcGxlIi...

The assertion must contain iss, sub, aud (the token endpoint) and exp. Google service accounts use this grant, and several platforms use it for server-to-server integrations. RFC 7523 also defines JWT client authentication, where the assertion is sent as client_assertion with client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer in place of a client secret. In both cases “bearer” refers to the assertion, and the access token that comes back may or may not be a JWT.

JWT With DPoP: Sender-Constrained Instead of Bearer

DPoP (Demonstrating Proof of Possession, RFC 9449) removes the bearer property. The client generates a key pair and, on every request, sends a small signed JWT called a DPoP proof. The authorisation server binds the access token to the client’s public key, so a stolen token is useless without the private key.

GET /orders HTTP/1.1
Host: api.example.com
Authorization: DPoP eyJhbGciOiJFUzI1NiIsInR5cCI6ImF0K2p3dCJ9...
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7...

# DPoP proof header          # DPoP proof payload
{                            {
  "typ": "dpop+jwt",           "jti": "e1j3V_bKic8-LAEB",
  "alg": "ES256",              "htm": "GET",
  "jwk": { "kty": "EC",        "htu": "https://api.example.com/orders",
    "crv": "P-256",            "iat": 1790240400,
    "x": "...", "y": "..." }   "ath": "..."
}                            }
  • ·The token endpoint returns "token_type": "DPoP", and the access token carries a cnf claim with jkt, the SHA-256 thumbprint of the client’s public key.
  • ·The API checks the proof signature against the embedded jwk, confirms the thumbprint matches cnf.jkt, and checks htm and htu against the actual request.
  • ·ath is the Base64url SHA-256 hash of the access token, tying the proof to that token; jti and iat limit replay, and servers can add a DPoP-Nonce.

Mutual-TLS certificate-bound tokens (RFC 8705) reach the same goal at the transport layer. Either way, the token is still a JWT; it has simply stopped being a bearer token.

ℹ
DPoP does not protect against an attacker who can run code in the client and use the private key directly. It stops tokens copied from logs, proxies or storage from being replayed elsewhere.

Summary

  • ·A bearer token grants access to whoever holds it (RFC 6750); a JWT is a signed token format (RFC 7519).
  • ·Most JWT access tokens are bearer tokens, but opaque bearer tokens and non-bearer JWTs both exist.
  • ·JWT bearer authentication reads the token from Authorization: Bearer and returns invalid_token or insufficient_scope challenges on failure.
  • ·The JWT bearer grant (RFC 7523) uses a signed JWT to obtain an access token or to authenticate a client.
  • ·DPoP (RFC 9449) binds a JWT to a client key, so possession of the token alone is no longer enough.
Ready to decode a token?
Use the free JWT decoder — paste any token for instant results, entirely in your browser.
Open JWT Decoder