Beta

Create a tenant

A new tenant starts with its own users, OAuth settings, audit history and logs. You are its first Tenant Admin.

BTL Admin

Limiting what stolen access can do

A stolen session has a shelf life

At 09:14, a relay page at a look-alike address passed Riley's password and authenticator-app code to Cedar Inc.'s identity provider and kept the session cookie it issued. Riley never held that session, so signing out on Riley's own laptop could not end it. Whatever limits Cedar placed on its sessions were the only limits on the attacker's copy.

Staying signed in described two of them. An idle timeout ends a session after a period without activity. An absolute timeout ends it after a fixed total time, however busy it is. Against theft, they behave very differently. A thief who keeps using a session keeps resetting its idle timer, so the idle timeout mainly protects sessions nobody is using. The absolute timeout bounds a copy in active use: once it passes, the thief has to sign in, with the password and the second factor.

Applications that keep people signed in for weeks usually renew a session quietly. A renewal must not extend a stolen copy too, so it either stays within the same absolute limit or issues a new identifier and treats any use of the replaced one as a warning sign, the idea behind refresh token rotation.

Noticing a reused refresh token

Applications that call APIs face the same question with tokens. Access tokens usually last minutes, and the client obtains new ones with a refresh token, as The refresh token grant describes. That makes the refresh token the long-lived credential worth stealing.

Rotation turns a refresh token into something close to a single-use value. Each time the client uses one, the authorization server returns a replacement and marks the old one as used. The tokens descended from one original grant form a token family, and at any moment only one member of the family should be valid, held by one party. Refresh tokens and rotation covers how a server keeps that record. The same record is what lets it notice a copy.

Suppose a thief has copied refresh token R1 from the photo application's phone app and uses it first. The thief receives R2, and the app is left holding R1, which is now used. The next time the app refreshes, it presents R1. The server sees a used token come back and cannot tell which holder is legitimate. It does not need to. Reuse detection revokes the whole family, so R2 stops working for the thief, and the app asks the person to sign in again. If the app refreshes first, the roles swap: the thief's attempt with R1 is the reuse, and the same revocation follows.

The photo app holds refresh token R1, and an attacker holds a copy. The attacker refreshes first with R1 and receives a new access token and R2, while the server marks R1 as used. When the app later refreshes with R1, the server detects that a used token was presented again and revokes the whole family. The app receives invalid_grant and asks the person to sign in again. The attacker's next refresh with R2 also receives invalid_grant. The photo app holds refresh token R1, and an attacker holds a copy. The attacker refreshes first with R1 and receives a new access token and R2, while the server marks R1 as used. When the app later refreshes with R1, the server detects that a used token was presented again and revokes the whole family. The app receives invalid_grant and asks the person to sign in again. The attacker's next refresh with R2 also receives invalid_grant.
The thief wins the first refresh but not the second. A used token coming back ends the whole family, whichever holder presents it.

Reuse detection fires only when both copies are used. If the real app has been uninstalled, a thief can keep rotating undisturbed, so a family also needs an absolute lifetime and an expiry after a period without use. Access tokens issued before the revocation keep working until they expire, which is one reason they are kept short. Refresh token theft walks through each of these races at the token endpoint.

Narrowing what a token can reach

A copy can do only what the original could. A token whose audience is https://api.photos.example is refused by the storage service at storage.photos.example, because it was not issued for that service. A token with the photos.read scope cannot delete photos. Each restriction shrinks what a thief gains. It also limits the damage when the leak happens at an API: an API whose logs are exposed, or which is itself compromised, holds tokens that work nowhere else.

Narrow tokens have to be requested on purpose. A client that calls two APIs can ask for a separate token for each instead of one token that both accept, and a token that only needs to read albums should not also carry permission to share them. Scopes and audiences explains how these values are requested and checked.

Binding access to a key

Every limit so far reduces what a copy is worth without making it useless, because a bearer token is complete in itself. Protecting credentials and messages mentioned the alternative, a token bound to a key pair. When the authorization server issues a sender-constrained token, also called a proof-of-possession token, it records which key the client holds, and the API accepts the token only together with fresh proof that the sender holds the matching private key. The client keeps the private key, ideally in storage that will not export it, as Storing and using keys describes. Someone who copies the token from a log has the token but not the key.

Two standards do this for OAuth. RFC 9449, OAuth 2.0 Demonstrating Proof of Possession (DPoP), has the client sign a short proof for each request with a key pair it generated. RFC 8705, which defines mutual TLS for OAuth, binds the token to the client certificate used on the TLS connection. Binding a token to a key and Certificate-bound access tokens show how each one works.

The same idea can protect browser sessions. In a device-bound session, the browser creates a key pair when the session starts and keeps the private key in the device's secure hardware where it can. The server issues short-lived cookies and renews them only when the browser proves it still holds the key. Malware that copies the cookies gets something that stops working within minutes, because the key it would need to renew them cannot be copied out.

Binding has two limits. Malware still running on the device can ask the hardware to sign, so binding stops replay from elsewhere but not misuse from the victim's own machine. And binding protects only what happens after issue. In a relayed sign-in like Riley's, the identity provider issues the session to the relay in the first place, so the session would be bound to the relay's key. Only a sign-in method that checks where it is used, such as a passkey, stops that.

Predict what happens when each copy below is replayed.

Replay a copied token

Simulation. These replay attempts are made up, and answering sends nothing. Each record describes a copied credential and how it is replayed; predict the result.

ITEM 1 OF 5

A bearer access token copied from an error report is replayed before it expires. What does the photo API do?
Credential:   bearer access token
Issued for:   aud https://api.photos.example, scope photos.read
Expires:      09:10 UTC
Copied:       09:02 UTC, from an error report
Replayed:     09:09 UTC, from 203.0.113.80
Request:      GET https://api.photos.example/albums/42/photos

Enable JavaScript to check an answer here, or open the explanation below.

Show the answer and why

Accepts it and returns the album, because the token is valid until 09:10. Nothing in a bearer token identifies its holder, so the copy works until it expires. That is why access tokens are kept short-lived.

ITEM 2 OF 5

The same copied token is sent to a different service at the photo company. What happens?
Credential:   bearer access token
Issued for:   aud https://api.photos.example, scope photos.read
Expires:      09:10 UTC
Replayed:     09:05 UTC, from 203.0.113.80
Request:      DELETE https://storage.photos.example/files/8812

Enable JavaScript to check an answer here, or open the explanation below.

Show the answer and why

Refused, because the token's audience is the photo API, not storage. The audience names the photo API only, and the scope allows reading only. A copy can do no more than the original could.

ITEM 3 OF 5

Malware copies the cookie of a device-bound session, and the thief replays it from another computer 20 minutes later. What does photos.example do?
Credential:   session cookie for photos.example, device-bound
Cookie life:  10 minutes, renewed only with a proof from the device key
Session:      started 08:30 UTC, absolute timeout 10 hours
Copied:       09:00 UTC, by malware on the owner's laptop
Replayed:     09:20 UTC, from 203.0.113.80
Show the answer and why

Refuses it: the copied cookie has expired, and renewing it needs the device key. The thief holds a cookie that lasted ten minutes and has no way to renew it, because the private key stays in the laptop's hardware. The session is still valid, but only for the device that holds the key.

ITEM 4 OF 5

A refresh token is copied from a backup of a phone that is then wiped, so the photo app never refreshes again. The thief starts using the copy. What happens?
Credential:   refresh token R1, rotated at each use
Family limit: ends 30 days after sign-in, or after 7 days unused
Copied:       2 days after sign-in, from a phone backup
Phone:        wiped the same day; the photo app never runs again
Thief:        refreshes with R1 the next day, then once a day

Enable JavaScript to check an answer here, or open the explanation below.

Show the answer and why

Accepted, with normal rotation, until the family's 30-day limit ends it. With only one holder, rotation looks normal. Daily use keeps the 7-day idle limit from firing, so the family's absolute lifetime is what finally ends the thief's access.

ITEM 5 OF 5

A session cookie copied by malware has been used every few minutes all day. What happens to the next request?
Credential:   session cookie for photos.example
Timeouts:     idle 1 hour, absolute 10 hours
Started:      08:30 UTC, sign-in by the account owner
Copy used:    every few minutes since 09:00 UTC, from 203.0.113.80
Next request: 18:45 UTC

Enable JavaScript to check an answer here, or open the explanation below.

Show the answer and why

Refused, because the session reached its absolute lifetime at 18:30. The absolute timeout bounds a copy in active use. To continue, the thief would have to sign in, which needs the owner's credentials.

Asking again before it matters

Some actions deserve an extra check whatever the state of the session: changing a password, adding a sign-in method, changing where payments go, or approving another application's access. Re-authentication asks the person to prove themselves again before the action proceeds. Authentication policy and SSO describes how a policy can require a recent sign-in or a stronger one.

Cedar had a check of this kind: adding an authenticator required a recent sign-in. At 09:20, from a hosting provider's address, the attacker used the stolen session to register an authenticator app of their own, and the check passed without a prompt because the session was six minutes old. A session stolen by a relay is always fresh, since the relay creates it at the moment of the theft. A recency check measures how long ago someone signed in, not who that someone was.

The check needed to ask how the session's sign-in was done, not only when. For actions that add a way into the account, a recent sign-in should count only if it used a phishing-resistant method, such as a passkey or a security key. Otherwise the service asks again, with one of those methods. The same rule lets the recovery walkthrough in Enrollment, replacement, and recovery accept a sign-in made moments earlier with a security key. Riley's 09:14 sign-in used a password and a code, so under that rule the 09:20 request would have stopped at a passkey prompt. Asking for the password and a code again would not have helped, because the relay could pass them along a second time, while a passkey will not sign for a look-alike address. The rule works only when the account has such a method enrolled, and when the policy refuses to fall back to a weaker one.

A notice through a separate channel helps as well. When a sign-in method is added, a message to the account's existing devices or verified email address gives the real owner a chance to react, even after the action has succeeded.

Choosing lifetimes people can live with

Every limit here has a cost for the people it protects. Sessions that end every hour mean signing in many times a day. People asked constantly learn to approve prompts without reading them, choose the quickest method on offer, and tick "remember this device" wherever they can.

The usual answer is to make the strict parts invisible and keep the visible parts rare. Access tokens can last minutes because refreshing them happens unnoticed. Re-authentication is saved for actions that change who can get in or where money goes, and signals such as an unfamiliar network can shorten a session or prompt a check in between.

Example lifetimes for a workforce identity provider
Credential or actionExample settingWhat limits a stolen copy
Access token10 minutesExpiry, audience and scope, and a key binding where supported
Refresh tokenRotated at each use; the family ends after 30 days, or after 7 days unusedReuse detection ends the family
Session on a managed laptop10 hours absolute, 1 hour idleThe absolute timeout, and a key held by the device
Session on an unrecognized device2 hours absolute, 30 minutes idleShorter timeouts and a new-device notice
Adding a sign-in methodAccept a recent passkey or security-key sign-in; otherwise ask for onePhishing-resistant re-authentication

The right numbers depend on what each application protects. The photo application can keep a customer signed in on their own phone for weeks, because rotation and a bound key leave a copy little to work with. A system that releases payments can ask for a passkey before each one, because the people using it expect that check and a stolen session there would cost far more.

Try it in the Lab

PUT IT INTO PRACTICE

Check your understanding

Try these questions before moving on. If an answer isn't right, use the feedback and try again.

0 of 2 answered correctly

Enable JavaScript to answer these questions and save progress in this browser.

QUESTION 1 OF 2An hour ago, the photo app's refresh token R1 was replaced by R2. Now R1 arrives at the token endpoint again. What should the authorization server do?

QUESTION 2 OF 2An attacker copies a DPoP-bound access token from a log and sends it to the photo API with a proof signed by their own key. Why is the request refused?

We value your privacy

We use cookies and similar technologies to enhance your browsing experience, and analytics to understand our traffic. By clicking "Allow All", you consent to optional analytics. Cookie Policy

Learn identity