Access Tokens in OAuth 2.0
The small piece of text that decides whether an app is allowed to touch your data — how it is created, how it is checked, and how it can go wrong.
Imagine you check into a hotel. At the front desk, you show your ID once, and in return the receptionist hands you a small plastic keycard. That keycard does not carry your name, your passport number, or your credit card details. It simply opens a specific door, for a specific number of days, and nothing else. Lose it, and whoever finds it can walk into your room until it expires or gets deactivated — but they still cannot get into the hotel safe, the gym, or any other guest’s room. That little piece of plastic is, in almost every meaningful way, an access token. This article is about that keycard: what it is made of, who hands it out, how doors check it, and what happens when it falls into the wrong hands.
1What Is an Access Token, Really?
Before touching architecture or security, it helps to nail down exactly what this “token” thing is, because the word gets thrown around loosely in tech conversations.
An access token is a piece of data that a client application presents to a server to prove that it has permission to act on behalf of a user, for a specific set of actions, for a specific window of time. It is not a password. It is not the user’s identity card. It is closer to a temporary, narrowly scoped permission slip.
Think about the difference between these two questions: “Who are you?” and “What are you allowed to do?” A password, or more generally an identity credential, answers the first question. An access token answers the second. This distinction is the single most important idea in the entire OAuth 2.0 specification, and almost every mistake beginners make comes from mixing the two up.
A backstage pass at a concert does not prove who you are — a bouncer checking IDs at the main gate does that. The backstage pass simply proves that whoever is holding it is allowed backstage, until the end of the show. If you hand your backstage pass to a friend, that friend can walk backstage too, no questions asked, because the pass itself is the permission. Access tokens work exactly the same way: whoever holds the token holds the access it grants, at least until the system that issued it decides otherwise.
OAuth 2.0, the framework this entire article revolves around, is not primarily a way to log users in. It is an authorization framework — a standardized way for one application (the client) to obtain limited access to a user’s resources that live on another service (the resource server), without ever seeing that user’s password. The access token is the physical output of that authorization process. Everything else in OAuth — redirect URLs, authorization codes, consent screens — exists purely to get one of these tokens safely into the hands of the right application.
Who Are You?
Verifying identity — usually a password, a fingerprint, or a one-time code. OpenID Connect, not plain OAuth 2.0, is the layer that formally handles this.
What Can You Do?
Granting permission to perform specific actions on specific resources. This is squarely OAuth 2.0’s job, and the access token is its output.
A ten-year-old could understand it this way: authentication is the school checking your student ID card at the gate. Authorization is the librarian deciding which shelves you are allowed to borrow from, and stamping a little card that says “this student can borrow from the science section until Friday.” The access token is that stamped card, not the student ID.
Scope is the formal name for “what the token is allowed to do.” A token might carry a scope of read:photos but not delete:photos, meaning the app holding it can view your photos but cannot remove them, no matter how the request is phrased.
Another key term worth planting early is bearer token. Most access tokens issued today are bearer tokens, meaning that whoever “bears” — carries — the token can use it. There is no additional proof of identity required at the moment of use; the token itself is the proof. This design choice is what makes OAuth simple and fast, and it is also exactly why protecting the token in transit and storage matters so much, a theme this article returns to repeatedly.
2Architecture & Components
Every OAuth 2.0 conversation involves four players. Learn their names once, and every diagram in the rest of this article will make sense immediately.
Resource Owner
Usually a human — you. You own the data (photos, emails, contacts) and you are the one who ultimately says “yes, this app may access my stuff.”
Client
The application requesting access — a mobile app, a website, a background job. In OAuth terminology, “client” almost never means “customer”; it means “the app doing the asking.”
Authorization Server
The gatekeeper. It authenticates the resource owner, shows the consent screen, and — if approved — mints the access token. Google’s accounts.google.com login screen is an authorization server in action.
Resource Server
The API that actually holds the protected data. It never issues tokens — it only checks the ones it receives before deciding whether to hand data back.
Picture a photo-editing app that wants to pull photos from your Google Drive. You (the resource owner) open the photo-editing app (the client). The app redirects you to Google’s login and consent screen (the authorization server). You approve. Google now hands the photo-editing app an access token. From that moment forward, whenever the photo-editing app wants your photos, it sends a request to the Google Drive API (the resource server) with that access token attached. The Drive API checks the token and, if it is valid and carries the right scope, returns your photos.
flowchart LR
RO["Resource Owner
(You)"] -->|1: Logs in & approves| AS["Authorization Server
(e.g. Google Accounts)"]
AS -->|2: Issues access token| C["Client
(Photo App)"]
C -->|3: Sends request + access token| RS["Resource Server
(Google Drive API)"]
RS -->|4: Validates token, returns data| C
C -->|5: Displays photos| RO
Notice something important in that diagram: the client never sees your Google password. It only ever sees the access token that the authorization server hands it after you approve. This separation — the client is trusted with a narrow token, never with your master credentials — is the entire reason OAuth 2.0 exists. Before frameworks like this became standard, it was common for apps to simply ask users to type their email password directly into a third-party form, a pattern security engineers now call the “password anti-pattern,” discussed further in Chapter 8.
The authorization server and the resource server are frequently the same company but still architecturally distinct services. Google’s login system and Google Drive’s API are operated by the same organization, yet they are separate systems talking to each other through tokens, exactly like two departments in a company that only communicate through official memos rather than shouting across the office.
3Internal Working: What Is Actually Inside a Token?
Access tokens come in two very different shapes internally, and the choice between them changes almost everything about how a system is built.
The OAuth 2.0 specification is deliberately silent about what an access token looks like on the inside — it only defines how tokens are requested and used, not their internal format. In practice, the industry settled on two dominant approaches.
Opaque Tokens
- A random string with no readable meaning, like
a1B9x... - The resource server cannot inspect it directly — it must call back to the authorization server to ask “is this still valid, and what can it do?”
- Easy to revoke instantly, since the authorization server is the single source of truth
Self-Contained (JWT) Tokens
- A structured, digitally signed token (typically a JSON Web Token) containing readable claims like user ID, scope, and expiry
- The resource server can validate it locally using a public key, with zero network call back to the authorization server
- Harder to revoke early, since the token is “self-contained” and valid until it naturally expires unless extra infrastructure is added
An opaque token is like a coat-check ticket with only a number printed on it — the cloakroom attendant has to look that number up in their own ledger to know whose coat it belongs to. A JWT is like a sealed, tamper-evident envelope with the coat owner’s name, the check-in time, and an official wax seal printed right on the outside — anyone holding a copy of the official stamp can verify the envelope is genuine just by looking at it, without calling the original desk at all.
Self-contained JWT access tokens generally consist of three parts joined by dots: a header (describing the signing algorithm), a payload (the actual claims — who the token is for, what scopes it grants, when it expires), and a signature (cryptographic proof that nobody tampered with the first two parts). Crucially, the payload of a standard JWT is only signed, not encrypted, meaning anyone who intercepts it can read the claims in plain text even though they cannot forge or alter them without detection. This is a frequent point of confusion for newcomers: a JWT is tamper-proof, not secret. Sensitive personal data should never be stuffed into a JWT payload assuming it is hidden from view.
| Property | Opaque Token | JWT Access Token |
|---|---|---|
| Readable by resource server? | No, must call authorization server | Yes, verified locally with a key |
| Instant revocation | Simple — delete from the store | Hard — needs a denylist or short lifetime |
| Network overhead per request | One extra call (introspection) | None after the public key is cached |
| Typical size | Small (a random string) | Larger (encodes claims) |
Validation of a token, regardless of format, always answers three questions: Is the signature (or the introspection response) genuine? Has the token expired? Does the token’s scope actually cover the action being requested? Only when all three checks pass does the resource server proceed. Skipping any one of these — a mistake covered in depth in Chapter 9 — is one of the most common real-world causes of OAuth-related security incidents.
4Data Flow & Lifecycle
A token is not a one-time gift. It is born, it lives, it does work, and eventually it dies — either quietly by expiring, or violently by being revoked.
The most common way an access token is born today is through the Authorization Code flow, the flow diagrammed briefly in Chapter 2. Let us walk through it end to end, following the token itself from birth to retirement.
sequenceDiagram
participant U as User (Browser)
participant C as Client App
participant AS as Authorization Server
participant RS as Resource Server
U->>C: Clicks "Connect my account"
C->>AS: Redirects user to login + consent screen
U->>AS: Logs in, approves scopes
AS->>C: Redirects back with a short-lived authorization code
C->>AS: Exchanges code + client secret for tokens
AS->>C: Returns access token (+ refresh token)
C->>RS: Calls API with access token
RS->>C: Validates token, returns protected data
Note over C,AS: Later, when access token expires
C->>AS: Sends refresh token
AS->>C: Issues a brand-new access token
Notice the two-step handshake in the middle: the client first receives a short-lived authorization code, not the access token itself, and only exchanges that code for the real token in a separate, back-channel request — typically from the client’s own server, away from the user’s browser. This extra step exists specifically so the access token never appears in a browser’s address bar or history, where it could be leaked through logs, bookmarks, or a nosy browser extension.
Once issued, an access token enters its active phase. Every time the client wants to call the resource server, it attaches the token, almost always inside an HTTP header that looks like Authorization: Bearer <token>. The resource server validates it on every single request — tokens are not “remembered” the way a browser cookie session might be; each request stands entirely on its own.
Access tokens are deliberately short-lived — often somewhere between five minutes and one hour. This is a safety design choice, not an oversight: the shorter the lifetime, the smaller the window of damage if a token is ever stolen. But constantly forcing the user to log in again every fifteen minutes would make for a miserable experience, so OAuth 2.0 introduces the refresh token — a longer-lived, more tightly guarded credential that the client can quietly exchange for a brand new access token, without bothering the user at all. Think of the access token as a day-pass and the refresh token as the membership card that lets you print a fresh day-pass at the front desk each morning without re-applying for membership.
Eventually, a token’s life ends in one of three ways: it simply expires once its lifetime is up; it is revoked, either by the user removing the app’s access from their account settings or by the authorization server proactively invalidating it after detecting suspicious behavior; or, in the case of a refresh token being used, the old access token is quietly superseded by a newer one.
5Advantages, Disadvantages & Trade-offs
No design choice in engineering is free. Access tokens solve real problems, but they trade some conveniences for stronger guarantees elsewhere.
Advantages
- The user’s password is never shared with third-party apps
- Access can be scoped narrowly — read-only, specific folders, specific time windows
- Access can be revoked for one app without affecting others
- Works uniformly across web, mobile, and machine-to-machine systems
Disadvantages & Trade-offs
- Bearer tokens are usable by anyone who obtains them, so theft is dangerous
- JWT-format tokens are hard to revoke instantly once issued
- More moving parts than a simple password check — more code, more places to make mistakes
- Short token lifetimes mean more refresh traffic and more places for bugs in refresh logic
The opaque-versus-JWT decision from Chapter 3 is itself a trade-off worth revisiting here in business terms. A company running a small number of services behind one authorization server often prefers opaque tokens, because instant revocation and centralized control matter more than shaving off a network hop. A company running thousands of independent microservices, on the other hand, often prefers JWTs, because forcing every single microservice to call back to a central authorization server on every request would create a crushing bottleneck at scale. Netflix-scale systems, serving requests across hundreds of independently deployed services, lean heavily toward self-contained tokens for exactly this reason — the cost of occasionally being unable to revoke a token instantly is accepted in exchange for massive scalability.
6Security
Because a bearer token is usable by anyone who holds it, the entire security model of OAuth 2.0 rests on keeping that token away from the wrong hands, for the shortest time possible.
The single biggest security risk in the access token lifecycle is token leakage — the token ending up somewhere it should not be. Common leak points include browser history (if the token ever appears in a URL instead of a header or POST body), server logs that carelessly record full request headers, poorly secured mobile app storage, and man-in-the-middle interception on connections that are not properly encrypted.
TLS Everywhere
Every single OAuth exchange — the login redirect, the code exchange, every API call carrying a token — must travel over HTTPS. A token sent over plain HTTP is as good as broadcast in public.
Least Privilege
Request only the scopes actually needed. A weather app asking for full email access is a red flag; narrow scopes limit the blast radius if a token is ever stolen.
Audience Restriction
A well-formed token should specify which resource server it is meant for. Without this, a token stolen from Service A could potentially be replayed against unrelated Service B.
PKCE
Proof Key for Code Exchange adds a one-time secret to the authorization code exchange, closing a gap that especially affects mobile and single-page apps where a traditional client secret cannot be safely hidden.
PKCE is like a two-part torn ticket stub used at some cinemas — the usher tears a ticket in half at the door, keeping one half and giving you the other. Even if someone manages to steal your half after you have already entered, it is useless to them without the matching half the usher is holding. In OAuth, the client generates a secret “verifier” before starting the login flow and sends only a scrambled version of it (the “challenge”) upfront; only the party holding the original verifier can later successfully exchange the authorization code for a token, blocking an attacker who merely intercepts the code mid-flight.
Another important security concept is token binding and audience validation on the resource server side. A resource server should never simply trust that any signed, unexpired token is good enough — it must also confirm the token was actually issued for it specifically, and that the required scope for the requested action is present. Skipping this check is what security researchers call a “confused deputy” vulnerability: a service ends up acting on behalf of a token that was never actually meant for it.
Refresh tokens deserve even stronger protection than access tokens, because a stolen refresh token lets an attacker mint fresh access tokens indefinitely. Best practice is to store refresh tokens only on a trusted backend server, never inside a mobile app’s local storage or a browser’s localStorage, and to rotate refresh tokens on every use so a stolen, already-used one becomes instantly worthless.
7Monitoring, Logging & Metrics
A token system that nobody watches is a system quietly waiting to fail. Good observability turns invisible token abuse into a visible, actionable alert.
Every authorization server should log token issuance events — which client requested a token, for which user, with which scopes, and from which IP address — without ever logging the token value itself in plain text. Logging the actual token string is a surprisingly common and dangerous mistake, since anyone with read access to logs would then be able to impersonate the token’s holder.
What Good Monitoring Looks Like
A sudden spike in token issuance requests from a single client, an unusual geographic jump between two consecutive uses of the same refresh token, or a rise in “invalid token” errors on the resource server are all classic early signals of credential stuffing, token theft, or a misbehaving client integration — and each should trigger an automated alert well before a human happens to notice.
On the metrics side, teams typically track: token issuance rate (tokens minted per minute), token validation latency (how long the resource server takes to check a token — critical for opaque tokens relying on a network round trip), refresh token usage patterns (is one refresh token being used from two different locations almost simultaneously — a strong sign of theft), and revocation counts (how often users or admins are actively pulling access, which can hint at a trust or security problem with a particular third-party client).
| Metric | What It Reveals |
|---|---|
| Token issuance rate per client | Sudden abuse or a runaway retry loop in a buggy integration |
| Validation failure rate | Expired tokens not being refreshed properly, or active attack attempts |
| Refresh token reuse detection | A strong, near-certain signal of token theft |
| Scope distribution | Whether apps are over-requesting permissions beyond what they need |
8Design Patterns & Anti-patterns
Good OAuth architecture follows a small number of well-worn patterns. Most incidents trace back to one of a small number of well-known anti-patterns.
The Pattern
The “password anti-pattern” — asking users to type their password for a third-party service (say, their email password) directly into a form owned by a different, unrelated application, which then stores or replays that password itself.
Why It Fails
The third-party app now holds the user’s master credential, with no ability to scope access, no easy way to revoke just that one integration, and a huge new target for attackers if that app is ever breached.
The Fix
Always redirect the user to the real authorization server’s own login page, exactly as the Authorization Code flow prescribes, so the third-party app only ever receives a scoped token, never the password itself.
The Pattern
Storing access tokens, and especially refresh tokens, in a browser’s localStorage for convenience.
Why It Fails
localStorage is readable by any JavaScript running on the page, meaning a single cross-site scripting bug anywhere on the site can leak every stored token to an attacker.
The Fix
Prefer HTTP-only, secure cookies for browser-based apps, which JavaScript cannot read directly, or keep long-lived tokens server-side entirely and only issue short-lived session identifiers to the browser.
On the healthier side of the ledger, a widely adopted pattern is the Backend-for-Frontend (BFF) design, where a single-page application never handles raw access or refresh tokens at all. Instead, a lightweight backend server sits between the browser and the authorization server, performs the entire token exchange itself, and hands the browser only an ordinary, tightly scoped session cookie. This pattern sidesteps almost the entire category of browser-based token theft in one architectural move, and is the design GitHub, Google, and most large-scale consumer platforms lean toward for their own web front ends.
Another healthy pattern is token introspection caching: rather than calling the authorization server on every single request for an opaque token, a resource server caches “this token is valid until timestamp X” for a very short window — often just a few seconds — trading a tiny amount of revocation delay for a significant reduction in network load, without fully giving up the instant-revocation benefit that made opaque tokens attractive in the first place.
9Best Practices & Common Mistakes
Most real-world OAuth incidents are not caused by exotic cryptographic attacks — they come from a handful of repeated, avoidable mistakes.
Validate Every Claim
Check signature, expiry, audience, and scope on every single request — never assume a previously validated token is still good later in the same session.
Log Raw Tokens
Never write the full token string into application logs, error trackers, or crash reports — mask or omit it entirely.
Rotate Refresh Tokens
Issue a brand-new refresh token every time one is used, and immediately invalidate the old one, so a stolen but already-used token becomes worthless.
Over-Scope Requests
Never request broader permissions than the feature genuinely needs — a photo-caption app has no reason to request access to contacts or calendar data.
Keep Lifetimes Short
Favor short access token lifetimes paired with a well-secured refresh mechanism, rather than one long-lived token that stays dangerous for weeks if stolen.
Trust Client-Side Storage
Never assume a mobile app’s or browser’s local storage is a safe place for a long-lived credential — treat every client device as potentially compromised.
When building an integration against any OAuth provider, read the provider’s own documented token lifetime and revocation behavior rather than assuming defaults from a different provider apply — Google, GitHub, and Microsoft each set slightly different default expiry windows and refresh policies.
10Real-World & Industry Examples
Access tokens are not a theoretical concept confined to specifications — they run some of the most heavily used integrations on the internet today.
Google’s “Sign in with Google” and Drive Integrations
When a third-party app requests Google Drive access, Google’s authorization server issues a scoped access token limited to exactly the folders or permission level the user approved — often distinguishing between read-only and full-edit scopes — and pairs it with a refresh token so the connection survives long after the initial sign-in.
GitHub’s Personal Access Tokens and OAuth Apps
GitHub issues both traditional OAuth access tokens for third-party integrations and separately scoped personal access tokens for direct API automation, letting developers narrowly restrict a token to, say, read-only access to a single repository rather than an entire account.
Uber’s Driver and Rider App Ecosystem
Uber’s internal microservices rely heavily on short-lived, self-contained access tokens passed between dozens of independently deployed backend services, allowing each service to validate a request locally at massive scale without hammering a central authorization service on every single ride-matching or payment call.
Netflix’s Internal Service-to-Service Authorization
Beyond user-facing logins, Netflix uses OAuth-style token issuance for machine-to-machine communication between its hundreds of internal microservices, where a token represents not a human user but a specific service’s permission to call another specific service — the same core token concept, applied to software talking to software instead of people talking to apps.
11Frequently Asked Questions
Not quite. A session cookie is typically tied to browser-based login state, managed automatically by the browser, and often scoped to a single site. An access token is a portable credential meant to be passed explicitly between a client and a resource server, frequently across different domains or even different companies entirely, and it deliberately carries scope and expiry information within its own design.
A token that never expires is a permanent liability — if it is ever stolen, leaked in a log, or left in an old device, it grants indefinite access with no natural expiry to cut off the damage. Short lifetimes paired with refresh tokens strike a balance between security and user convenience.
The authorization server invalidates that app’s refresh token immediately, and typically adds its existing access tokens to a denylist or simply lets them expire naturally within minutes, after which any further API call from that app using the old token fails.
No — a properly issued access token is tied to the specific client that requested it, along with the specific scopes that client was granted. Sharing a token between unrelated apps breaks the entire accountability and scoping model OAuth 2.0 is built around.
By default, no — a standard JWT is signed, not encrypted, meaning its contents can be read by anyone who intercepts it, even though they cannot alter it undetected. Sensitive data should never be placed directly inside a JWT payload on the assumption that it is hidden.
12Summary and Key Takeaways
What to Remember
- An access token answers “what can this app do,” not “who is this person.” Authentication and authorization are different jobs, and OAuth 2.0 handles the second one.
- Four roles drive every OAuth flow: the resource owner, the client, the authorization server, and the resource server — learn these names once and every diagram becomes readable.
- Tokens come in two shapes: opaque tokens that need a lookup, and self-contained JWTs that can be verified locally — each trades revocation speed against scalability.
- Tokens have a full lifecycle: issued through an authorization code exchange, used repeatedly while active, refreshed quietly behind the scenes, and eventually expired or revoked.
- Bearer tokens are only as safe as their handling — TLS everywhere, narrow scopes, short lifetimes, and careful storage are what actually keep them safe, not the OAuth specification alone.
- Most real incidents come from a small set of known anti-patterns — the password anti-pattern, unsafe browser storage, and missing audience checks chief among them.
- This is not theoretical. Every “Sign in with Google,” every third-party app connected to a GitHub repository, and huge swaths of internal microservice traffic at companies like Uber and Netflix run on exactly this token model, day and night, at enormous scale.