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:

  1. Resource Owner: The user who owns or controls the data.
  2. Client: The application requesting access.
  3. Authorization Server: The system that authenticates the user and issues tokens.
  4. 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 iss claim identify the expected issuer?
  • Audience: Does the aud claim 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.