Access Token
An access token is a string a client presents to a service in place of credentials, representing permission it was granted earlier. RFC 6749 defines it as “a string representing an access authorization issued to the client,” and RFC 6750 describes the arrangement: “tokens are issued to clients by an authorization server with the approval of the resource owner. The client uses the access token to access the protected resources hosted by the resource server.”
The word “token” is heavily overloaded, so it is worth separating the three common uses. This one is a credential. A language model’s token is a unit of text. And tokenization in payments or privacy work means replacing a sensitive value with a surrogate. Nothing below applies to the other two.
Three parties are involved, and keeping them distinct explains most of the design. An authorization server issues, a client presents, and a resource server — your service, or something in front of it — decides whether to accept. This entry is about that third role, because that is where the checking has to happen.
Two kinds of token, two ways to validate
Before any checklist, establish which kind of token you are holding, because the validation procedure differs completely. The specification allows both: a token “may denote an identifier used to retrieve the authorization information or may self-contain the authorization information in a verifiable manner (i.e., a token string consisting of some data and a signature).”
- Self-contained tokens carry their claims and a signature. The resource server validates them locally against the issuer’s published keys — no call to the authorization server on the request path, at the cost of not knowing about a revocation until expiry.
- Reference tokens are opaque handles — the specification notes the string “is usually opaque to the client” — and mean nothing on their own. The resource server validates one by asking the authorization server what it stands for, which gives current state including revocation, at the cost of a dependency in the request path.
A second axis cuts across that one: whether holding the token is sufficient to use it. A bearer token can be used “by any party in possession of such a token.” Sender-constrained schemes bind a token to a client key at issuance and require “that the client proves possession of the corresponding private key when using the token,” so a stolen token alone is not enough. Which axis your API sits on is a decision to document, since it changes what a leak costs.
The specification is explicit that none of this is fixed: “access tokens can have different formats, structures, and methods of utilization (e.g., cryptographic properties) based on the resource server security requirements.”
Verifying a self-contained token
For the signed, self-contained case — the common one — accepting a token means five checks, and a token that has only been decoded has not been verified.
- Signature, against the issuer’s published keys. Reading the contents of an unverified token and trusting them is the most direct way to accept a forged one.
- Issuer. A token minted by a different authority must be rejected rather than trusted for having a familiar shape.
- Audience — who the token was issued for. This is the check most often skipped, and skipping it means a token issued for another service is a valid credential for yours. If several of your services share an issuer, any of them can then impersonate a caller to the others.
- Expiry, with a stated clock tolerance rather than an accidental one.
- Scope, which says what class of operation the client may attempt — coarse permission, not a decision about a particular record.
The specified failure responses mark the boundary precisely. A failure carries “the appropriate HTTP status code (typically, 400, 401, 403, or 405)” with an error code. invalid_token covers a token that “is expired, revoked, malformed, or invalid for other reasons,” answered with 401 — after which “the client MAY request a new access token and retry the protected resource request.” insufficient_scope covers the case where “the request requires higher privileges than provided by the access token,” answered with 403, optionally naming “the scope necessary to access the protected resource.”
Read as instructions to the client, the two codes mean different things: 401 says get a new token and try again, 403 says a new token of the same kind will not help. Conflating them produces clients that loop fetching credentials against a permission problem.
One more specified behaviour is commonly implemented wrongly, in the direction of being too silent. A protected resource must always send a challenge when credentials are missing or insufficient: the resource server “MUST include the HTTP ‘WWW-Authenticate’ response header field,” and “all challenges defined by this specification MUST use the auth-scheme value ‘Bearer’.” What is omitted for an unauthenticated request is only the diagnostic detail — with no authentication information present the server “SHOULD NOT include an error code or other error information.”
The specification’s own example makes the shape clear: 401 Unauthorized with WWW-Authenticate: Bearer realm="example" — a header telling the caller how to authenticate, and no explanation of what went wrong. A bare 401 with no header is not the specified behaviour, and it is how a new consumer ends up guessing at your authentication scheme.
What a token cannot tell you
A verified token with the right scope establishes that this caller was authorized for this class of operation. A general scope such as invoices:read does not establish that they may act on the specific thing they named, because a scope of that shape describes a class of operation rather than an object. Treating it as sufficient is the standard route to a caller reading someone else’s record by editing an identifier in a URL.
That is a limit on coarse scopes rather than on tokens as such, and the difference is worth keeping straight. A token can carry object-level authorization when it is issued to do so: RFC 9396 defines a request parameter for detailed authorization, and its content reaches the resource server either inside a JWT access token or through introspection, so a resource server can be handed the specific targets and actions a grant covers. What you then owe that data is ordinary token verification applied to it — issuer, audience, signature, expiry, and whatever revocation or current-state check your risk demands — plus the discipline of not widening it on the way through.
What no token settles is the part that depends on the row as it is now.
Claims can narrow the question without answering it. A trusted tenant or organization claim lets a caller be refused for anything outside its tenant, which is a real and worthwhile filter. What no claim can settle is whether the invoice is in a state where this operation applies, or whether the ownership recorded when the token was issued still holds — a token minted five minutes ago describes the world five minutes ago. Those depend on the current row, so that part of the check belongs where the row is read and the change commits; anywhere else and the answer can be stale by the time it is used. This is a statement about freshness and business conditions, not about what a token is technically capable of expressing.
This is the difference between authentication, which a token settles, and authorization, of which a token settles as much as it was issued to carry — a class of operation with a plain scope, or specific targets with a richer grant, and never the conditions that only the current data knows. Services that treat any valid token as sufficient for whatever was named in the path have made a real check answer a different question.
Lifetime, revocation, and leakage
A bearer token can be used by whoever holds it, and that single property drives the operational practices below. (Sender-constrained tokens weaken the premise but do not remove the need for any of it.)
Short lifetimes exist because revocation is hard. A service that validates a token by signature alone cannot know it was revoked a minute ago, so the expiry is the practical bound on a stolen token’s usefulness. Checking revocation on every request — by asking the issuer, or by consulting a revocation list — buys immediacy at the cost of a dependency in the request path, and which trade-off is right depends on what the token can do. The surrounding process, and what has to happen when access must end rather than expire, is access revocation.
Scope is the other lever, and it is the one under your control at issue time. A token carrying every permission because that was easier makes its theft maximally useful; narrow scopes per client are the same least privilege reasoning applied to credentials that travel.
Four recurring mistakes
- Decoding without verifying. Convenience libraries make reading claims easy and verification optional-looking.
- No audience check, which turns every service sharing an issuer into a potential impersonator of the others.
- Tokens in URLs. Query strings reach access logs, referrer headers, and browser history, and a credential in a log is a credential you have distributed.
- Treating an internal network as authentication. A service that accepts unauthenticated requests from inside a boundary has made the boundary the credential — see trust boundary.
Where these checks are performed — once at the edge, in each service, or both — is a design decision with a clear default: uniform verification at the edge, and each service still authorizing its own data access as though the edge were not there. That division is worked through in API Management and Delivering a Service.
References: RFC 6750, The OAuth 2.0 Authorization Framework: Bearer Token Usage; RFC 9396, OAuth 2.0 Rich Authorization Requests (both checked September 2026).
Discover more from Insightful Data Lab
Subscribe to get the latest posts sent to your email.
