Bearer Tokens Based Authentication
Bearer Token–Based API Authentication and Authorization
Bearer tokens are widely used to protect APIs. After a client obtains an access token from an authorization server, it presents that token whenever it calls a protected API.
The term bearer means that whoever possesses the token can use it. The caller normally does not need to prove possession of an additional cryptographic key. This makes bearer tokens convenient, but it also means that a stolen token may be used by an attacker until it expires or is revoked.
A bearer token is not an OAuth grant type. It is a method of presenting and using an access token. An application can obtain a bearer access token through several OAuth flows, including:
- Authorization Code Grant: Used when a human user is involved.
- Client Credentials Grant: Used for machine-to-machine communication without a user.
Other grant types and extensions can also issue bearer tokens. The grant determines how the token is obtained; the bearer-token mechanism determines how the token is used.
How a Bearer Token Is Sent to an API
The client normally places the access token in the HTTP Authorization header:
GET /api/accounts HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOi...
The API extracts the token, validates it, checks its permissions and decides whether to process the request.
Bearer tokens should not be placed in URLs because URLs may be stored in browser history, proxy logs, server logs, monitoring systems and referrer headers. The HTTP Authorization header is the preferred transmission method described in RFC 6750.
Bearer tokens must always be protected with HTTPS. Because possession is generally sufficient to use them, disclosure of a token is similar to disclosure of a temporary password.
Are Bearer Tokens Always JWTs?
No. A bearer token can be:
- An opaque, randomly generated string
- A JSON Web Token, or JWT
- Another token format understood by the authorization server and resource server
An opaque token usually requires the API to call an introspection endpoint or consult a shared authorization store. A JWT is self-contained and can carry claims such as:
- Token issuer
- Intended audience
- User or client identity
- Granted scopes
- Roles
- Issue time
- Expiration time
The words bearer token describe how the token can be used—not how it is formatted.
OAuth Authorization Code Grant
When a human user is involved, the Authorization Code Grant with PKCE is generally the recommended OAuth flow.
This flow allows the user to authenticate directly with the authorization server or Identity Provider. The application never needs to collect or process the user’s password.
Participants in the Flow
The Authorization Code flow involves four roles:
- Resource Owner: The user who owns or controls the data.
- Client: The application requesting access.
- Authorization Server: The system that authenticates the user and issues tokens.
- Resource Server: The API that receives and validates the access token.
For example, a user may authorize a financial application to read account information from a banking API. The user authenticates with the bank’s authorization server, while the financial application receives a token with limited permission to call the API.
Authorization Code Flow: Step by Step
Step 1: The user starts at the application
The user opens the client application and selects an action such as:
Sign in
or:
Connect my account
The application redirects the user’s browser to the authorization server.
Step 2: The application sends an authorization request
The authorization request typically includes:
client_id: Identifies the application.redirect_uri: Specifies where the authorization server should return the browser.response_type=code: Requests an authorization code.scope: Identifies the permissions being requested.state: Binds the response to the user’s original browser session.code_challenge: Used by PKCE to protect the authorization code.code_challenge_method=S256: Indicates that the PKCE challenge was created using SHA-256.
An example request might resemble:
GET /authorize?
response_type=code&
client_id=customer-portal&
redirect_uri=https://app.example.com/callback&
scope=accounts.read%20profile&
state=RANDOM_SESSION_VALUE&
code_challenge=PKCE_CHALLENGE&
code_challenge_method=S256
Step 3: The user authenticates
The authorization server authenticates the user.
Depending on organizational policy and risk, this may include:
- Username and password
- Authenticator application
- Passkey
- FIDO2 security key
- Biometric authentication
- Conditional Access
- Step-up MFA
The client application does not receive the user’s password. Authentication takes place at the authorization server.
Step 4: The user grants consent
When consent is required, the authorization server shows the permissions requested by the application.
For example:
This application is requesting permission to read your profile and account information.
The user can approve or deny the request.
In enterprise environments, an administrator may grant consent in advance, so the individual user may not see a consent screen.
Step 5: The authorization server returns a code
After successful authentication and authorization, the authorization server redirects the browser to the application’s registered callback URL:
https://app.example.com/callback?
code=TEMPORARY_AUTHORIZATION_CODE&
state=RANDOM_SESSION_VALUE
The returned value is an authorization code, not an access token.
The code should be:
- Short-lived
- Single-use
- Bound to the client
- Bound to the exact redirect URI
- Protected through PKCE
Because the access token is not returned directly through the browser, the flow reduces the risk of exposing it through browser history, redirects or front-end scripts.
Step 6: The application exchanges the code for tokens
The client sends the authorization code to the authorization server’s token endpoint over a protected back-channel connection.
POST /token HTTP/1.1
Host: authorization.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=TEMPORARY_AUTHORIZATION_CODE&
redirect_uri=https://app.example.com/callback&
client_id=customer-portal&
code_verifier=ORIGINAL_PKCE_VERIFIER
A confidential server-side client may also authenticate itself using a client secret, a signed JWT, mutual TLS or another supported client-authentication method.
If the code and PKCE verifier are valid, the authorization server may return:
{
"access_token": "ACCESS_TOKEN_VALUE",
"token_type": "Bearer",
"expires_in": 900,
"refresh_token": "REFRESH_TOKEN_VALUE",
"scope": "accounts.read profile"
}
Step 7: The client calls the API
The application presents the access token to the resource server:
GET /api/accounts HTTP/1.1
Host: api.example.com
Authorization: Bearer ACCESS_TOKEN_VALUE
The API validates the token before returning data.
Why PKCE Is Important
PKCE—Proof Key for Code Exchange—protects the Authorization Code flow against authorization-code interception and injection attacks.
Before starting the flow, the client creates:
- A secret, random
code_verifier - A derived
code_challenge
The challenge is sent with the authorization request. The original verifier is sent later when exchanging the authorization code for a token.
An attacker who intercepts the authorization code cannot redeem it without the corresponding verifier.
Current OAuth security guidance requires public clients to use PKCE and recommends it for confidential clients as well. The S256 challenge method should be used. These recommendations appear in the IETF’s OAuth 2.0 Security Best Current Practice, RFC 9700.
Public and Confidential Clients
The Authorization Code flow can be used by both public and confidential clients.
Public clients
Public clients cannot safely maintain a client secret. Examples include:
- Single-page applications
- Mobile applications
- Desktop applications
A secret embedded in browser code or a distributed mobile application can be extracted and therefore cannot reliably prove the application’s identity.
Public clients must use Authorization Code with PKCE.
Confidential clients
Confidential clients run in environments where credentials can be protected. Examples include:
- Server-side web applications
- Backend services
- Applications using a Backend-for-Frontend pattern
These clients can authenticate to the token endpoint. Modern implementations should still use PKCE as an additional layer of protection.
Client Credentials Grant
The Client Credentials Grant is used when no human user is involved.
Typical examples include:
- A scheduled job calling an internal API
- A backend service calling another microservice
- An EC2 workload calling a partner API
- An automated reporting system retrieving data
- A deployment pipeline calling a management API
In this flow, the client acts on its own behalf. The access token represents the application or workload—not an end user.
Client Credentials Flow: Step by Step
Step 1: The client authenticates to the authorization server
The application sends a request directly to the token endpoint:
POST /token HTTP/1.1
Host: authorization.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&
scope=orders.read
The client must also authenticate itself.
Possible authentication methods include:
- Client ID and client secret
- Signed client assertion using
private_key_jwt - Mutual TLS
- A workload or managed identity
For higher-security environments, asymmetric authentication methods such as mutual TLS or signed JWT assertions are preferable to long-lived shared secrets.
Step 2: The authorization server issues an access token
After verifying the client’s identity and permissions, the authorization server returns an access token:
{
"access_token": "SERVICE_ACCESS_TOKEN",
"token_type": "Bearer",
"expires_in": 900,
"scope": "orders.read"
}
Step 3: The service calls the API
GET /api/orders HTTP/1.1
Host: api.example.com
Authorization: Bearer SERVICE_ACCESS_TOKEN
The API validates the token and confirms that the calling service has the required scope.
Authorization Code vs. Client Credentials
| Characteristic | Authorization Code with PKCE | Client Credentials |
|---|---|---|
| Human user involved | Yes | No |
| Token represents | User authorization delegated to a client | Application or workload |
| Browser redirect required | Yes | No |
| User authentication | Performed by the authorization server | Not applicable |
| Consent may be required | Yes | Normally configured administratively |
| Typical use | Web, mobile and browser applications | Service-to-service API calls |
| Refresh token | May be issued | Usually unnecessary |
| MFA applicable | Yes, during user authentication | No human MFA; secure workload authentication instead |
| Recommended protection | PKCE, state, exact redirect validation | Strong client authentication and short-lived credentials |
How the API Should Validate a Bearer Token
Receiving a token is not sufficient. The API must validate it before trusting any claim it contains.
For a JWT access token, the API should verify:
- Signature: Was the token signed by a trusted authorization server?
- Issuer: Does the
issclaim identify the expected issuer? - Audience: Does the
audclaim identify this API? - Expiration: Has the token expired?
- Not-before time: Is the token valid yet?
- Scopes: Does the token permit the requested operation?
- Roles or permissions: Is the caller authorized for the resource?
- Token type: Is the token intended to be used as an access token?
- Algorithm: Is the signing algorithm explicitly permitted?
The API should not accept a token merely because it is structurally valid or has a valid signature. A token issued for API A must not automatically be accepted by API B.
For opaque tokens, the API can use token introspection or another trusted validation mechanism supplied by the authorization server.
Security Best Practices
Use short-lived access tokens
Bearer tokens should have limited lifetimes. A shorter lifetime reduces the window in which a stolen token can be abused.
Restrict the audience
Issue tokens for a specific API or a small, clearly defined set of APIs. The API must reject tokens intended for another resource server.
Apply least-privilege scopes
A token should carry only the permissions required for its intended operation. Avoid broad scopes such as unrestricted read/write access when narrower permissions are possible.
Never log complete tokens
Access and refresh tokens must not appear in:
- Application logs
- Browser console output
- URLs
- Analytics systems
- Error messages
- Support tickets
- Distributed tracing data
If a token identifier is needed for troubleshooting, record only a non-sensitive fingerprint or carefully selected claim.
Protect tokens in storage
Server-side tokens should be stored in secure memory or an encrypted credential store.
Browser applications should minimize direct token exposure. For sensitive applications, a Backend-for-Frontend architecture can keep access and refresh tokens on the server and expose only a protected session cookie to the browser.
Protect refresh tokens
Refresh tokens are often longer-lived than access tokens and therefore require stronger protection. For public clients, current security guidance requires refresh-token rotation or sender-constrained refresh tokens.
Validate redirect URIs exactly
Authorization servers should compare redirect URIs using exact matching. Wildcards and open redirectors can allow authorization codes or tokens to be redirected to an attacker.
Use state and PKCE
Use transaction-specific state values to bind authorization responses to the initiating browser session. Use PKCE with the S256 method to prevent intercepted or injected authorization codes from being redeemed.
Consider sender-constrained tokens
Traditional bearer tokens can be replayed by anyone who steals them. Higher-security systems can use sender-constrained access tokens, such as:
- Mutual TLS-bound access tokens
- DPoP-bound access tokens
These mechanisms require the client to prove possession of a cryptographic key when using the token, reducing the usefulness of a stolen token. RFC 9700 recommends considering sender-constrained access tokens to mitigate token theft and replay.
Common Misunderstandings
“OAuth authenticates the user”
OAuth is primarily an authorization framework. It grants a client limited access to protected resources.
If an application needs federated user authentication or “Sign in with…” functionality, it should use OpenID Connect, which adds an identity layer and ID tokens to OAuth.
“Every JWT is a bearer token”
A JWT is a token format. Bearer describes how a token is presented and used. A JWT may be used as a bearer token, but not every JWT is a bearer access token.
“Client Credentials represents a user”
It does not. A Client Credentials token normally represents the application, service or workload. If a downstream API needs user context, use an appropriate delegation or token-exchange pattern rather than silently treating the service as the user.
“MFA should be added to Client Credentials”
MFA is designed for human authentication. Machine identities should instead use strong workload credentials, short-lived tokens, managed identities, mutual TLS or signed client assertions.
Conclusion
Bearer tokens provide a practical way to authorize API requests, but they must be handled like sensitive credentials. Anyone who obtains a traditional bearer token may be able to use it.
The correct OAuth flow depends primarily on who or what is requesting access:
- Use Authorization Code with PKCE when a human user is involved and an application needs delegated access.
- Use Client Credentials when a service or workload is acting on its own behalf.
- Use OpenID Connect in addition to OAuth when the application needs to authenticate the user.
- Consider sender-constrained tokens where the consequences of token theft justify stronger protection.
The grant flow determines how an access token is issued. The bearer-token model determines how that token is presented to an API. Keeping those two concepts separate is the foundation of a secure OAuth design.