{
  "schemaVersion": "1.0",
  "code": "700081",
  "name": null,
  "title": "AADSTS700081: Refresh token reached its maximum lifetime (fix)",
  "url": "https://brambrinkmeier.com/entra/aadsts/700081/",
  "alternates": {
    "markdown": "https://brambrinkmeier.com/entra/aadsts/700081.md",
    "json": "https://brambrinkmeier.com/api/aadsts/700081.json",
    "html": "https://brambrinkmeier.com/entra/aadsts/700081/"
  },
  "description": "AADSTS700081: the refresh token hit its maximum lifetime, often the 24-hour SPA cap. Not in Microsoft's error list. Expected; re-sign-in. KQL.",
  "summary": "AADSTS700081 means the app tried to use a refresh token that is past its maximum lifetime. Users report it in single-page apps, where Microsoft caps refresh tokens at 24 hours. Microsoft's error reference doesn't list it. It is an expected part of the token lifecycle with low attack value. The app must send the user through sign-in again.",
  "inMicrosoftReference": false,
  "unlistedChecked": "2026-10-30",
  "meaningText": "Microsoft's [error code reference](https://learn.microsoft.com/entra/identity-platform/reference-error-codes) doesn't list AADSTS700081 (checked 30 October 2026).\n\nThe wording below comes from a user report. A developer on the MSAL.js GitHub repository posted:\n\n> AADSTS700081: The refresh token has expired due to maximum lifetime. The token was issued on 2021-07-22T15:38:26.0767832+00:00 and the maximum allowed lifetime for this application is 1.00:00:00.\n\nThe app was a single-page app (SPA) using MSAL.js 2 and MSAL React. It showed up during an `acquireTokenSilent` call. A commenter in the thread said this error should be handled inside `acquireTokenSilent` and not returned to the app. `1.00:00:00` is one day.\n\nMicrosoft documents the 24-hour cap, without the code number. Its refresh token page:\n\n> Refresh tokens sent to a redirect URI registered as `spa` expire after 24 hours. Additional refresh tokens acquired using the initial refresh token carry over that expiration time, so apps must be prepared to rerun the authorization code flow using an interactive authentication to get a new refresh token every 24 hours.\n\nThe same page lists 24 hours for single-page apps, 24 hours for apps that use the email one-time passcode flow, and 90 days for all other scenarios.\n\nSo rotating the refresh token doesn't help. Each new refresh token inherits the original expiry. When it passes, the app has to start a new sign-in.\n\nMicrosoft's error reference lists a code for the same situation, AADSTS700084: *\"The refresh token was issued to a single page app (SPA), and therefore has a fixed, limited lifetime of {time}, which can't be extended.\"* Our reading: 700081 and 700084 describe the same cap. Which code you get in which case isn't documented. The GitHub report is from 2021, so 700084 may be the newer wording.\n\nDon't confuse it with [700082](https://brambrinkmeier.com/entra/aadsts/700082/), which is a refresh token that expired from **inactivity**. 700081 is about **total age**, however active the user was.",
  "sections": [
    {
      "heading": "What does AADSTS700081 mean?",
      "text": "Microsoft's [error code reference](https://learn.microsoft.com/entra/identity-platform/reference-error-codes) doesn't list AADSTS700081 (checked 30 October 2026).\n\nThe wording below comes from a user report. A developer on the MSAL.js GitHub repository posted:\n\n> AADSTS700081: The refresh token has expired due to maximum lifetime. The token was issued on 2021-07-22T15:38:26.0767832+00:00 and the maximum allowed lifetime for this application is 1.00:00:00.\n\nThe app was a single-page app (SPA) using MSAL.js 2 and MSAL React. It showed up during an `acquireTokenSilent` call. A commenter in the thread said this error should be handled inside `acquireTokenSilent` and not returned to the app. `1.00:00:00` is one day.\n\nMicrosoft documents the 24-hour cap, without the code number. Its refresh token page:\n\n> Refresh tokens sent to a redirect URI registered as `spa` expire after 24 hours. Additional refresh tokens acquired using the initial refresh token carry over that expiration time, so apps must be prepared to rerun the authorization code flow using an interactive authentication to get a new refresh token every 24 hours.\n\nThe same page lists 24 hours for single-page apps, 24 hours for apps that use the email one-time passcode flow, and 90 days for all other scenarios.\n\nSo rotating the refresh token doesn't help. Each new refresh token inherits the original expiry. When it passes, the app has to start a new sign-in.\n\nMicrosoft's error reference lists a code for the same situation, AADSTS700084: *\"The refresh token was issued to a single page app (SPA), and therefore has a fixed, limited lifetime of {time}, which can't be extended.\"* Our reading: 700081 and 700084 describe the same cap. Which code you get in which case isn't documented. The GitHub report is from 2021, so 700084 may be the newer wording.\n\nDon't confuse it with [700082](https://brambrinkmeier.com/entra/aadsts/700082/), which is a refresh token that expired from **inactivity**. 700081 is about **total age**, however active the user was."
    },
    {
      "heading": "Is it an attack?",
      "text": "No. It is the token lifecycle working as designed.\n\n| Pattern | Likely meaning |\n|---|---|\n| SPA users get it once a day | The 24-hour cap. Expected. The app should redirect to sign-in. |\n| Users see an error page instead of a quiet re-sign-in | Probably the app doesn't handle the error (unconfirmed). |\n| One user, many rows, no success after | The app keeps retrying the expired token. A bug in the app. |\n| Expired tokens presented from IPs your users don't use | Possibly someone replaying an old stolen token (unconfirmed). Rare; check the IPs. |"
    },
    {
      "heading": "How do I find it in my logs (KQL)?",
      "text": "Microsoft doesn't document which sign-in table 700081 is logged in. Our reading: refresh token redemption is non-interactive, so `AADNonInteractiveUserSignInLogs` is the first place to look. Check where it lands:\n\n```kusto\nunion withsource=SourceTable SigninLogs, AADNonInteractiveUserSignInLogs, AADServicePrincipalSignInLogs\n| where TimeGenerated > ago(30d)\n| where ResultType in (\"700081\", \"700084\", \"700082\")\n| summarize Rows = count(), Users = dcount(UserPrincipalName) by SourceTable, ResultType\n```\n\nWhich apps, and whether users recover. A user who hits 700081 and never succeeds on the same app that day points at an app that doesn't fall back to sign-in:\n\n```kusto\nlet expired =\n    union SigninLogs, AADNonInteractiveUserSignInLogs\n    | where TimeGenerated > ago(7d)\n    | where ResultType == \"700081\"\n    | summarize FirstFail = min(TimeGenerated) by UserPrincipalName, AppId, AppDisplayName;\nlet successes =\n    union SigninLogs, AADNonInteractiveUserSignInLogs\n    | where TimeGenerated > ago(7d)\n    | where ResultType == \"0\"\n    | summarize LastSuccess = max(TimeGenerated) by UserPrincipalName, AppId;\nexpired\n| join kind=leftouter successes on UserPrincipalName, AppId\n| where isnull(LastSuccess) or LastSuccess < FirstFail\n| summarize UsersNotRecovering = dcount(UserPrincipalName) by AppDisplayName, AppId\n| sort by UsersNotRecovering desc\n```\n\nNon-interactive rows are aggregated, so counts are not request counts."
    },
    {
      "heading": "How do I fix AADSTS700081?",
      "text": "**For the user:** sign in to the app again. A page reload is often enough. If you see this every day, the app should handle it for you; tell its owner.\n\n**For developers:**\n1. Treat it as \"sign-in needed\", not as a failure. Microsoft: *\"apps must be prepared to rerun the authorization code flow using an interactive authentication to get a new refresh token every 24 hours.\"*\n2. With MSAL.js, catch the error from `acquireTokenSilent` and fall back to `acquireTokenRedirect` or `acquireTokenPopup`. In the GitHub thread, a commenter said MSAL should fall back to a hidden iframe when the refresh token expires. The reporter's fix was letting the app's page load in an iframe (`X-Frame-Options: SAMEORIGIN`).\n3. Expect silent renewal to fail in browsers that block third-party cookies. Microsoft: *\"The browser must visit the sign-in page in a top-level frame to show the login session.\"*\n4. If you need refresh tokens that live longer, the client isn't an SPA. Redeem tokens on a backend registered as a **Web** app. Don't move SPA tokens to a server; that gives [9002327](https://brambrinkmeier.com/entra/aadsts/9002327/).\n\n**For admins:** nothing to change in the tenant. Microsoft says the SPA lifetime is fixed and *\"can't be extended\"* (700084 wording). Filter the code out of failure dashboards."
    }
  ],
  "microsoftWording": {
    "text": "AADSTS700081: The refresh token has expired due to maximum lifetime. The token was issued on 2021-07-22T15:38:26.0767832+00:00 and the maximum allowed lifetime for this application is 1.00:00:00.",
    "source": "Quoted on the page from the lab or from user reports; the code is not in Microsoft's error code reference",
    "sourceUrl": null
  },
  "securityReading": {
    "category": {
      "id": "token",
      "label": "Tokens and sessions"
    },
    "kind": {
      "id": "interrupt",
      "label": "Interrupt (an expected extra step, not a failure)"
    },
    "attackSignal": {
      "level": "low",
      "label": "Low: usually configuration or user error"
    },
    "mitre": [],
    "tables": [],
    "isItAnAttack": "No. It is the token lifecycle working as designed.\n\n| Pattern | Likely meaning |\n|---|---|\n| SPA users get it once a day | The 24-hour cap. Expected. The app should redirect to sign-in. |\n| Users see an error page instead of a quiet re-sign-in | Probably the app doesn't handle the error (unconfirmed). |\n| One user, many rows, no success after | The app keeps retrying the expired token. A bug in the app. |\n| Expired tokens presented from IPs your users don't use | Possibly someone replaying an old stolen token (unconfirmed). Rare; check the IPs. |",
    "conditionalAccess": null
  },
  "kql": [
    {
      "title": null,
      "leadIn": "Microsoft doesn't document which sign-in table 700081 is logged in. Our reading: refresh token redemption is non-interactive, so `AADNonInteractiveUserSignInLogs` is the first place to look. Check where it lands:",
      "query": "union withsource=SourceTable SigninLogs, AADNonInteractiveUserSignInLogs, AADServicePrincipalSignInLogs\n| where TimeGenerated > ago(30d)\n| where ResultType in (\"700081\", \"700084\", \"700082\")\n| summarize Rows = count(), Users = dcount(UserPrincipalName) by SourceTable, ResultType"
    },
    {
      "title": null,
      "leadIn": "Which apps, and whether users recover. A user who hits 700081 and never succeeds on the same app that day points at an app that doesn't fall back to sign-in:",
      "query": "let expired =\n    union SigninLogs, AADNonInteractiveUserSignInLogs\n    | where TimeGenerated > ago(7d)\n    | where ResultType == \"700081\"\n    | summarize FirstFail = min(TimeGenerated) by UserPrincipalName, AppId, AppDisplayName;\nlet successes =\n    union SigninLogs, AADNonInteractiveUserSignInLogs\n    | where TimeGenerated > ago(7d)\n    | where ResultType == \"0\"\n    | summarize LastSuccess = max(TimeGenerated) by UserPrincipalName, AppId;\nexpired\n| join kind=leftouter successes on UserPrincipalName, AppId\n| where isnull(LastSuccess) or LastSuccess < FirstFail\n| summarize UsersNotRecovering = dcount(UserPrincipalName) by AppDisplayName, AppId\n| sort by UsersNotRecovering desc"
    }
  ],
  "kqlSectionText": "Microsoft doesn't document which sign-in table 700081 is logged in. Our reading: refresh token redemption is non-interactive, so `AADNonInteractiveUserSignInLogs` is the first place to look. Check where it lands:\n\n```kusto\nunion withsource=SourceTable SigninLogs, AADNonInteractiveUserSignInLogs, AADServicePrincipalSignInLogs\n| where TimeGenerated > ago(30d)\n| where ResultType in (\"700081\", \"700084\", \"700082\")\n| summarize Rows = count(), Users = dcount(UserPrincipalName) by SourceTable, ResultType\n```\n\nWhich apps, and whether users recover. A user who hits 700081 and never succeeds on the same app that day points at an app that doesn't fall back to sign-in:\n\n```kusto\nlet expired =\n    union SigninLogs, AADNonInteractiveUserSignInLogs\n    | where TimeGenerated > ago(7d)\n    | where ResultType == \"700081\"\n    | summarize FirstFail = min(TimeGenerated) by UserPrincipalName, AppId, AppDisplayName;\nlet successes =\n    union SigninLogs, AADNonInteractiveUserSignInLogs\n    | where TimeGenerated > ago(7d)\n    | where ResultType == \"0\"\n    | summarize LastSuccess = max(TimeGenerated) by UserPrincipalName, AppId;\nexpired\n| join kind=leftouter successes on UserPrincipalName, AppId\n| where isnull(LastSuccess) or LastSuccess < FirstFail\n| summarize UsersNotRecovering = dcount(UserPrincipalName) by AppDisplayName, AppId\n| sort by UsersNotRecovering desc\n```\n\nNon-interactive rows are aggregated, so counts are not request counts.",
  "fix": {
    "short": "User: sign in to the app again; a page reload is often enough. Developer: treat it as sign-in needed. With MSAL.js, catch it from `acquireTokenSilent` and fall back to `acquireTokenRedirect` or `acquireTokenPopup`. Admin: nothing to change; the SPA lifetime can't be extended.",
    "steps": "**For the user:** sign in to the app again. A page reload is often enough. If you see this every day, the app should handle it for you; tell its owner.\n\n**For developers:**\n1. Treat it as \"sign-in needed\", not as a failure. Microsoft: *\"apps must be prepared to rerun the authorization code flow using an interactive authentication to get a new refresh token every 24 hours.\"*\n2. With MSAL.js, catch the error from `acquireTokenSilent` and fall back to `acquireTokenRedirect` or `acquireTokenPopup`. In the GitHub thread, a commenter said MSAL should fall back to a hidden iframe when the refresh token expires. The reporter's fix was letting the app's page load in an iframe (`X-Frame-Options: SAMEORIGIN`).\n3. Expect silent renewal to fail in browsers that block third-party cookies. Microsoft: *\"The browser must visit the sign-in page in a top-level frame to show the login session.\"*\n4. If you need refresh tokens that live longer, the client isn't an SPA. Redeem tokens on a backend registered as a **Web** app. Don't move SPA tokens to a server; that gives [9002327](https://brambrinkmeier.com/entra/aadsts/9002327/).\n\n**For admins:** nothing to change in the tenant. Microsoft says the SPA lifetime is fixed and *\"can't be extended\"* (700084 wording). Filter the code out of failure dashboards."
  },
  "labReproduced": null,
  "labSectionText": null,
  "labRuns": [],
  "verification": "documentation",
  "related": [
    "700082",
    "70008",
    "50058",
    "9002327"
  ],
  "coveredBy": [],
  "sources": [
    {
      "title": "Microsoft Entra authentication and authorization error codes",
      "url": "https://learn.microsoft.com/entra/identity-platform/reference-error-codes"
    },
    {
      "title": "GitHub: Failed authentication. AADSTS700081: The refresh token has expired due to maximum lifetime (microsoft-authentication-library-for-js #3931)",
      "url": "https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/3931"
    },
    {
      "title": "Refresh tokens in the Microsoft identity platform",
      "url": "https://learn.microsoft.com/en-us/entra/identity-platform/refresh-tokens"
    },
    {
      "title": "Microsoft identity platform and OAuth 2.0 authorization code flow",
      "url": "https://learn.microsoft.com/entra/identity-platform/v2-oauth2-auth-code-flow"
    },
    {
      "title": "SigninLogs table reference (Azure Monitor)",
      "url": "https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/signinlogs"
    }
  ],
  "published": "2026-10-30",
  "lastUpdated": "2026-10-30",
  "lastVerified": null,
  "license": "CC BY 4.0",
  "licenseUrl": "https://creativecommons.org/licenses/by/4.0/",
  "licenseNote": "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.",
  "attribution": "Bram Brinkmeier, \"AADSTS700081: Refresh token reached its maximum lifetime (fix)\", https://brambrinkmeier.com/entra/aadsts/700081/ (CC BY 4.0)",
  "microsoftErrorLookup": "https://login.microsoftonline.com/error?code=700081"
}
