# AADSTS70008: The refresh token has expired or was revoked (fix)

Canonical: https://brambrinkmeier.com/entra/aadsts/70008/
By Bram Brinkmeier. JSON: https://brambrinkmeier.com/api/aadsts/70008.json

**Short answer:** AADSTS70008 means an app tried to use a refresh token or grant that is no longer valid. Microsoft's text names inactivity; the code name also says revoked. It is usually part of the normal token lifecycle and has low attack value. The app should send the user to an interactive sign-in. Hunt it in the non-interactive sign-in logs.

**Fix:** User: sign in again when the app asks; if it keeps failing without asking, sign out of the app and back in. Developer: treat 70008 as interactive sign-in needed and fall back from the silent call to an interactive one. Don't retry the same refresh token.

| Field | Value |
|---|---|
| Code | AADSTS70008 |
| Microsoft name | ExpiredOrRevokedGrant |
| Category | Tokens and sessions |
| Type | Failure |
| Attack signal | Low: usually configuration or user error |
| Where to look | `AADNonInteractiveUserSignInLogs`, `SigninLogs` |
| In Microsoft's error reference | Yes |
| Verification | documentation |
| Last updated | 2026-10-30 |

## What does AADSTS70008 mean?

Microsoft's description:

> **ExpiredOrRevokedGrant**: The refresh token has expired due to inactivity. The token was issued on XXX and was inactive for a certain amount of time.

An app presented a refresh token (a "grant") to get a new access token, and Microsoft Entra ID refused it. The description talks about inactivity. The code name, *ExpiredOrRevokedGrant*, also covers revocation. Microsoft doesn't say which cases return 70008 rather than a more specific code.

Two sibling codes in the same reference table are more specific:

- [700082](https://brambrinkmeier.com/entra/aadsts/700082/) **ExpiredOrRevokedGrantInactiveToken**, with almost the same text, which Microsoft calls an *"Expected part of the token lifecycle"*.
- [50173](https://brambrinkmeier.com/entra/aadsts/50173/), a grant revoked after a credential change: *"The user might have changed or reset their password."*

Our reading: 70008 is the more generic form of the same event. Treat it like 700082 unless the context points to revocation.

**How long refresh tokens live.** Microsoft's refresh token page gives the defaults: *"24 hours for single-page applications"*, 24 hours for the email one-time passcode flow, and *"90 days for all other scenarios."* The configurable token lifetimes page lists a non-configurable *Refresh Token Max Inactive Time* of 90 days. Tokens *"can be revoked by the sign-in service at any time before their expiration"*, for example when the user changes their password or an admin revokes all refresh tokens for the user.

## Is it an attack?

Usually not. A refresh token that expired or was revoked gives nobody access. The common sources are a device or app that wasn't used for a long time, and an app that keeps an old token after a password change or a session revocation.

| Pattern | Likely meaning |
|---|---|
| 70008 from a user's known device, then an interactive success | The user came back after a long break, or after a password change, and signed in again. Benign. |
| 70008 for many users of one app, all at once | Our interpretation: a mass revocation (for example after an incident response) or an app holding tokens far past their lifetime. Check what happened that day. |
| 70008 shortly after you revoked a user's sessions | The revocation is working. The old token was refused. |
| 70008 from an IP or device the user doesn't normally use | Someone presented an old refresh token from an unfamiliar place. Reasoning, not a documented detection: if the token was stolen, expiry or revocation is what stopped it. Worth a short look. |

**IP caveat.** Microsoft's non-interactive sign-ins page says the IP of non-interactive sign-ins by confidential clients *"doesn't match the actual source IP of where the refresh token request is coming from. Instead, it shows the original IP used for the original token issuance."* An unfamiliar IP on a 70008 row is a meaningful signal mainly for public clients (desktop, mobile and browser apps).

## How do I find it in my logs (KQL)?

Refresh token redemption is a non-interactive sign-in (*"A client app uses an OAuth 2.0 refresh token to get an access token"* is one of Microsoft's examples), so expect most rows in `AADNonInteractiveUserSignInLogs`. Microsoft doesn't document the table for this code specifically, so check:

```kusto
union withsource=SourceTable SigninLogs, AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(7d)
| where ResultType == "70008"
| summarize Rows = count(), Users = dcount(UserPrincipalName) by SourceTable
```

Baseline by app and client:

```kusto
AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(7d)
| where ResultType == "70008"
| summarize Rows = count(), Users = dcount(UserPrincipalName) by AppDisplayName, ClientAppUsed
| sort by Rows desc
```

70008 from an IP the user had no successful sign-in from in the previous 13 days (14 days back up to yesterday). Use it for triage, not as an alert on its own, and keep the confidential-client caveat above in mind:

```kusto
let lookback = 14d;
let knownIPs =
    union SigninLogs, AADNonInteractiveUserSignInLogs
    | where TimeGenerated between (ago(lookback) .. ago(1d))
    | where ResultType == "0"
    | distinct UserPrincipalName, IPAddress;
AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(1d)
| where ResultType == "70008"
| join kind=leftanti knownIPs on UserPrincipalName, IPAddress
| summarize Rows = count(), Apps = make_set(AppDisplayName, 5), UserAgents = make_set(UserAgent, 3)
          by UserPrincipalName, IPAddress
| sort by Rows desc
```

Non-interactive rows are aggregated when application, user, IP address, status and resource ID match, so `Rows` is not a request count. `ResultType` is a **string** column, so compare it with `"70008"`, not `70008`.

## How does Conditional Access affect it?

- Refresh token lifetimes can't be changed with policy. Microsoft says that *"As of January 30, 2021, refresh and session token lifetimes are no longer configurable through token lifetime policies."*
- To control how often users sign in, Microsoft points to Conditional Access sign-in frequency. A refresh refused because of sign-in frequency is a different code, [70043](https://brambrinkmeier.com/entra/aadsts/70043/).

## How do I fix AADSTS70008?

**For the user:** sign in again when the app asks. If the app keeps failing without asking, sign out of it and back in.

**For developers:** treat 70008 as *"interactive sign-in needed"*. Microsoft's guidance for refresh tokens is that the app *"should handle such revocations gracefully by redirecting the user to an interactive sign-in prompt"*. With MSAL, catch the error from the silent call and fall back to an interactive call. Don't retry the same refresh token. For single-page apps, plan for the 24-hour limit: Microsoft says they *"must be prepared to rerun the authorization code flow using an interactive authentication"*.

**For admins:**
1. Nothing is broken in the normal case. Exclude `"70008"` from failure-rate dashboards.
2. If one app produces most of the rows and users complain, the app probably doesn't fall back to an interactive sign-in. Report it to the developer.
3. If the third query shows rows from unfamiliar public-client IPs, check that user's other sign-ins from the same IP. If anything succeeded there, treat it as a possible token theft and [revoke the user's sessions](https://brambrinkmeier.com/guides/revoke-sessions-and-verify-entra-id/).

## Evidence

Not reproduced in the lab yet; the page is based on Microsoft's documentation.

## Sources

1. [Microsoft Entra authentication and authorization error codes](https://learn.microsoft.com/en-us/entra/identity-platform/reference-error-codes)
2. [Refresh tokens in the Microsoft identity platform](https://learn.microsoft.com/en-us/entra/identity-platform/refresh-tokens)
3. [Configurable token lifetimes in the Microsoft identity platform](https://learn.microsoft.com/en-us/entra/identity-platform/configurable-token-lifetimes)
4. [Non-interactive user sign-ins](https://learn.microsoft.com/en-us/entra/identity/monitoring-health/concept-noninteractive-sign-ins)
5. [AADNonInteractiveUserSignInLogs table reference (Azure Monitor)](https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/aadnoninteractiveusersigninlogs)
6. [SigninLogs table reference (Azure Monitor)](https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/signinlogs)

## Related codes

[AADSTS700082](https://brambrinkmeier.com/entra/aadsts/700082/), [AADSTS50173](https://brambrinkmeier.com/entra/aadsts/50173/), [AADSTS70043](https://brambrinkmeier.com/entra/aadsts/70043/), [AADSTS50133](https://brambrinkmeier.com/entra/aadsts/50133/), [AADSTS700081](https://brambrinkmeier.com/entra/aadsts/700081/), [AADSTS7000110](https://brambrinkmeier.com/entra/aadsts/7000110/)

## License and citation

CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). CC BY 4.0 covers the author's own text and classification fields. Quoted Microsoft error wording belongs to Microsoft, is quoted with its source and can change.

Cite as: Bram Brinkmeier, "AADSTS70008: The refresh token has expired or was revoked (fix)", https://brambrinkmeier.com/entra/aadsts/70008/ (CC BY 4.0), last updated 2026-10-30.
