The step-up authentication series: Part 1: Step-up MFA with authentication contexts · Part 2: Defeating token replay with end-to-end step-up · Part 3: Limiting token lifetimes in Entra ID (you are here)
Here is a sequence we have walked more than one client through. At 09:14 a user runs an installer they should not have, and an infostealer lifts every token it can find. At 09:31 the EDR quarantines the binary. By 09:48 the response is textbook: password reset, Revoke-MgUserSignInSession, device isolated. Someone asks the question the whole bridge call exists to answer. Are we good?
The honest answer: the refresh tokens and session cookies are dead, and the access tokens stolen at 09:14 keep working. Password resets do not reach them. Revocation does not reach them, except on the handful of resources that support Continuous Access Evaluation. For everything else, a stolen access token dies of exactly one thing, old age, and in a default tenant that takes up to 90 minutes from the moment it was minted.
Part 2 of this series built the architectural fix for high-risk operations: never let the valuable token exist where it can be stolen, and make each authorisation single-use. This article is about everything else. The rest of your estate runs on ordinary bearer tokens, and Entra ID gives you a specific, finite set of levers that control how long those tokens, and the credentials that silently re-mint them, stay alive. Most tenants we review have touched none of them, or worse, touched the wrong one for the problem they had (sign-in frequency on All Users, Part 1's opening mistake).
If you only remember one thing from this article: on most resources, an Entra ID access token is an unrevocable promise for its entire lifetime. Every lever below makes those promises shorter, rarer, or killable. None of them binds a token to an operation. Know which problem you are solving before you reach for a dial.
TLDR
- Access tokens live 60 to 90 minutes by default and cannot be revoked mid-life on resources without CAE. Revocation kills refresh tokens and sessions, not tokens already issued.
- Configurable Token Lifetime (CTL) policy can shrink an application's access token lifetime to a floor of 10 minutes. It is the only direct "make tokens shorter" control left. Refresh and session token lifetimes have not been configurable since 30 January 2021.
- Refresh tokens silently re-mint access tokens, same claims, fresh
iat, no human involved. They only exist whenoffline_accessis requested, and MSAL requests it by default. Scope hygiene is a real lever. - Sign-in frequency controls how often Entra demands fresh authentication. "Every time" means "when the session is evaluated", with a five-minute grace floor, not every click. The application has to drive the re-evaluation.
- CAE makes first-party Microsoft resources revocable in near-real time, and in exchange extends token lifetime to up to 28 hours. Your custom API gets nothing from CAE unless you build support for it.
- Token Protection binds sign-in sessions to the device, but only PRTs, only Windows, only native apps. DPoP and mTLS-bound tokens are not available in Entra. There is still no sender-constrained access token for web apps, which is why Part 2 exists.
Tokens Everywhere
Before turning dials, know what Entra hands out at sign-in and how long each piece lives in a default tenant:
The randomised access token lifetime is documented, and deliberate:
"When using the Microsoft identity platform, the default lifetime for an access token is assigned a random value ranging between 60-90 minutes (75 minutes on average)." Microsoft Learn: Configurable token lifetimes
The structural point sits in the arrows of that diagram. The access token is the short-lived artefact, but three longer-lived credentials can each mint a new one silently. Limiting token lifetime is therefore two different jobs. Shorten the authentication assurance itself, and control the things that renew the assurance.
Lever 1: Configurable Access Token Lifetime policy
What it controls: the lifetime of access tokens (and ID and SAML tokens) issued for a specific application or service principal. The floor is 10 minutes, the ceiling 24 hours.
A finance API whose access tokens live 10 minutes instead of 75 has shrunk every stolen-token window, and the legitimate-cache replay window from Part 2's demo, by a factor of seven. Creating one is two Graph calls:
Connect-MgGraph -Scopes "Policy.ReadWrite.ApplicationConfiguration"
$policy = New-MgPolicyTokenLifetimePolicy -DisplayName "Finance API - 10 minute access tokens" `
-Definition @('{"TokenLifetimePolicy":{"Version":1,"AccessTokenLifetime":"00:10:00"}}') `
-IsOrganizationDefault:$false
# Assign it to the API's app registration (object ID, not app ID)
New-MgApplicationTokenLifetimePolicyByRef -ApplicationId $apiAppObjectId `
-BodyParameter @{ "@odata.id" = "https://graph.microsoft.com/v1.0/policies/tokenLifetimePolicies/$($policy.Id)" }What it buys you: a hard cap on how long any single access token for that resource works, attacker-held or cache-held.
What it leaves open: everything that re-mints. A refresh token in the client's hands will fetch a fresh 10-minute token, with the same claims, every 10 minutes, forever, silently. Shorter tokens without refresh-token discipline mostly buys you more /token traffic. Two operational notes before you set everything to 10 minutes: token requests for that app increase proportionally, and short lifetimes make you more sensitive to Entra availability hiccups, because clients lose the buffer of a long-lived cached token. We typically reserve sub-15-minute lifetimes for the handful of resources where the replay window genuinely matters.
One scoping reminder. Do not set this as organisation default. Scope it to the applications that need it, or you will pay the friction and traffic cost estate-wide for a window only one API cares about.
And the lever that is not here: refresh token and session lifetimes left CTL on 30 January 2021. Microsoft's pointer is explicit:
"You can not set token lifetimes for refresh tokens and session tokens. To control how frequently users are required to sign in, use Conditional Access sign-in frequency instead." Microsoft Learn: Configurable token lifetimes
W
Lever 2: stop handing out refresh tokens
What it controls: whether a refresh token exists at all for a given flow. This is the bluntest and most direct lever we have at the cost of end-user experience.
A refresh token is only issued when the request includes the offline_access scope. No offline_access, no refresh token, and the access token the client holds is the last one it gets without going back through an interactive (or SSO) trip to Entra. This matters for step-up scenarios in a specific way that Part 1 readers will recognise. When a refresh token re-mints an access token, the new token carries the same acrs and amr claims with a fresh iat. This is the documented mechanism behind Part 1's warning that iat measures the token, not the authentication. A "freshly issued" token proves nothing about fresh authentication while a refresh token is alive.
The catch is who requests offline_access. You probably do, without knowing:
MSAL adds
openid,profile, andoffline_accessto every token request by default. — Microsoft Learn: MSAL.js scopes and resources
So "we don't use refresh tokens" is a claim worth a gut-check in your own captures: pull a /token response for your app and look for the refresh_token field. Removing it means overriding library defaults per request, not flipping a tenant setting.
What it buys you: no silent re-minting for the flows you strip it from. For a high-assurance leg (Part 2's transaction flow does exactly this), the access token becomes a short-lived, non-renewable proof.
What it leaves open: the current access token still works until expiry, and stripping offline_access from ordinary interactive apps trades security for prompts very quickly. Use it per-flow, not per-tenant. Worth knowing: SPAs already have refresh tokens capped at 24 hours by platform policy, against 90 days rolling for web and native clients.
This is a very blunt tool.
Lever 3: sign-in frequency
What it controls: how old an authentication Entra will accept before forcing the user to authenticate again.
This is the lever most tenants have already touched, and the one most often pointed at the wrong problem. Part 1 opened with the canonical misuse: sign-in frequency, one hour, All Users, as imitation step-up. It taxes every user of the app and still leaves an hour-wide replay window on the operation you cared about. Pointed at the session lifetime problem, though, it is exactly the right tool, and it comes in two modes.
Periodic reauthentication (every N hours or days) caps how long a sign-in session, and the refresh chain hanging off it, survives without a human re-proving themselves. This is the modern replacement for the retired refresh token lifetime settings, and the right control for "a stolen session on an unmanaged device should not work next Tuesday".
Every time is the sharp end, and its semantics are precise enough to quote rather than paraphrase:
"When administrators select every time, it requires full reauthentication when the session is evaluated. ... The policy factors in five minutes of clock skew, so that it doesn't prompt users more often than once every five minutes." — Microsoft Learn: Configure adaptive session lifetime policies
Two consequences hide in that sentence. First, "when the session is evaluated" is not "every click". If nothing forces a new token request, the user is not prompted. Microsoft is explicit that the control "works best when the resource has the logic to identify when a client should get a new token", which translates to: your application initiates the re-evaluation, the policy guarantees the prompt happens. That division of labour is the entire reason Part 2's flow sends the user interactively to /authorize per transaction and lets an Every time policy do the prompting. Second, the five-minute floor means even Every time tolerates a five-minute-old authentication. For per-operation guarantees, validate auth_time server-side as well (lever 5).
The configuration we actually recommend, and the marriage of this series' parts: scope Every time to an authentication context, not to an application. The context's CA policy carries the sign-in frequency, the app requests the context only for sensitive operations (Part 1's pattern), and the friction lands only where the risk is. As a bonus, this constrains opportunistic acrs issuance, since Entra only adds the claim opportunistically while the policy's sign-in frequency is still satisfied.
What it buys you: an upper bound on authentication age, and a guaranteed prompt when your app forces the round trip.
What it leaves open: tokens already issued (a one-hour access token issued one minute before the session was revoked still has 59 minutes), and any binding to what the user is doing. Fresh is not the same as "for this".
Lever 4: Continuous Access Evaluation
What it controls: whether a token can be killed before it expires.
Everything so far shortens windows. CAE is the one mechanism that closes a window early. Resources that support it accept long-lived tokens and, in exchange, subscribe to revocation events from Entra. Disable the user, reset the password, flag the session, or have the token show up from outside your named locations, and a CAE-aware resource rejects the token in near-real time instead of at expiry.
The trade is counterintuitive enough to fail a gut-check in design reviews, so say it plainly: enabling CAE makes access tokens longer-lived, up to 28 hours, and that is fine, because the lifetime stops being the control. Revocation is. An 09:48 revocation actually reaches the stolen token at 09:49, instead of the token coasting to 10:44.
Now the scope, because this is where teams over-credit it:
"Continuous access evaluation is implemented by enabling services, like Exchange Online, SharePoint Online, and Teams, to subscribe to critical Microsoft Entra events." — Microsoft Learn: Continuous access evaluation
First-party resources, plus Microsoft Graph. Your custom finance API is not on the list and never will be by default. An API can participate (it means accepting CAE-capable tokens, handling claims challenges, and processing revocation signals), but that is engineering work you do, not a toggle you flip. Until you build it, the stolen-token story for your LOB API is unchanged by CAE, and Part 2's architecture remains the control for high-risk operations. Strict location enforcement, CAE's hardest mode, stops access the moment a token is used from an IP outside your named locations, and inherits the same caveats: IP-based named locations only, first-party resources only, and real operational risk if your egress IPs are not meticulously inventoried.
What it buys you: revocation that actually reaches issued tokens, on first-party resources.
What it leaves open: every resource that is not CAE-aware, which includes your custom APIs unless you build it in.
Lever 5: max_age, prompt=login, and auth_time
What it controls: authentication freshness for a single request, asserted by the client and verifiable by your server.
OIDC defines max_age (re-authenticate if the last authentication is older than N seconds) and requires the auth_time claim in ID tokens when max_age is used. On paper, max_age=0 is per-request step-up. In Entra practice, we do not recommend hanging anything load-bearing on max_age. Entra processes it, but its behaviour has community-documented quirks and no crisp Microsoft statement of exact semantics. Treat it as belt-and-braces and test it in your own tenant before trusting it.
The reliable version of this lever has three parts, and Part 2 uses all of them:
prompt=loginon the authorisation request forces fresh credential entry. Blunt, but unambiguous.- Sign-in frequency Every time on the relevant CA policy (lever 3) makes the prompt a policy guarantee rather than a client request.
auth_time, validated server-side, is the proof. Opt the claim in viaoptionalClaimson the app registration, then reject any token whoseauth_timepredates the operation the user is performing. A staleauth_timeis exactly what an opportunistically satisfied or replayed authentication looks like.
What it buys you: verifiable authentication freshness per request, on your terms.
What it leaves open: binding to the operation's content. Freshness says "now". It does not say "for this $1.5M, to this payee". That gap is Part 2's whole subject.
Lever 6: the one you want doesn't exist yet
The clean fix for stolen bearer tokens is to make tokens useless to anyone but the rightful client: sender-constrained, cryptographically bound tokens. For completeness, the June 2026 state of that lever in Entra ID:
- DPoP (RFC 9449): not supported. Entra does not issue DPoP-bound access tokens.
- mTLS-bound access tokens for confidential clients: investigated by Microsoft, not available.
- Token Protection (Conditional Access session control): generally available and worth deploying where it fits, but read the label. It binds the Primary Refresh Token to the device's TPM, requires Entra ID P1, and covers Windows devices and native applications. Microsoft's documentation is direct that browser-based applications are not supported. It protects the sign-in session on managed Windows endpoints. It does not bind the access tokens your web app presents to your API.
Which means the honest summary of this entire article is unchanged from Part 2: for a web application, a stolen bearer access token works until it expires or (on first-party resources) is revoked. The levers above make that sentence as short as possible. Only architecture makes it stop mattering. Watch this space, because sender-constraining is the obvious next move for Microsoft to make, and this section gets rewritten the day they ship it.
The lever board
| Lever | What it buys you | What it leaves open | Cost |
|---|---|---|---|
| CTL policy (10 min floor) | Hard cap on any single token's life for that app | Silent re-mint via refresh token | More token traffic, outage sensitivity |
Drop offline_access per flow | No silent re-mint for that leg | Current token until expiry | Interactive trip per token |
| Sign-in frequency (periodic) | Caps session and refresh-chain age | Issued tokens, operation binding | Periodic prompts, broad if mis-scoped |
| Sign-in frequency (Every time, on an auth context) | Guaranteed prompt per app-driven evaluation | Five-minute floor, issued tokens | Prompt per sensitive operation |
| CAE | Near-real-time revocation reaches issued tokens | Custom APIs unless you build support | Tokens extend to 28 h, IP hygiene needed |
prompt=login + server-side auth_time | Verifiable per-request freshness | Operation content binding | Per-request prompt, custom validation code |
| Token Protection | PRT bound to TPM on Windows | Web apps and browsers entirely | P1 licensing, platform limits |
| Transaction-bound step-up (Part 2) | Auth bound to one operation, once | Server compromise, consent-screen content | Engineering work, ceremony per operation |
Putting it together
The profile we deploy most often, by tier:
- Estate-wide: leave CAE on (it is the default) and treat its 28-hour tokens as fine, because revocation is the control. Periodic sign-in frequency for unmanaged devices. Baseline MFA unconditionally, no exceptions for "trusted" anything.
- Sensitive applications: CTL at 10 to 15 minutes on the specific resource. Audit which flows actually receive refresh tokens and strip
offline_accesswhere it is doing nothing for the user. Verifyacrsandauth_timearrive in your tokens before you need them. - High-risk operations: authentication context, Every time sign-in frequency on its policy, server-side
auth_timevalidation, and if the operation can move money or destroy data, the full Part 2 transaction-bound flow.
And one incident-response footnote that this article's opening scene earns: write down, today, which of your resources are CAE-aware and what your shortest and longest access token lifetimes are. That list is the difference between "are we good?" having an answer at 09:50 and having one at 10:44.
Mandatory plug
Token lifetime reviews are some of the highest-leverage half-days we spend with clients: pull real tokens, decode what your apps actually receive, time the actual windows, and find the refresh tokens nobody knew they were issuing. If you want that picture of your own tenant, get in touch. And if you want to capture the token conversations yourself, SuiteAuth's closed beta is open for waitlist sign-up.




