Stolen Refresh Tokens in Microsoft 365: How to Spot Residential-Proxy Token Replay in the Entra Sign-In Logs
A stolen password shows up in the interactive sign-in log. A stolen refresh token lives in the non-interactive log, where almost nobody looks. In a case we documented in [a redacted post-incident report](/blog/what-a-microsoft-365-post-incident-report-should-contain-lessons-from-a-bec-case), the attacker refreshed an Outlook token about three times a day from 88 consumer-ISP addresses for four weeks, every request carrying a valid MFA claim, with no new risk detection. Here is what that looks like, how to hunt for it, and what would have ended it on day one.
How a refresh token gets stolen, and why MFA reports success
In the sample case the user opened a phishing link on her phone. The page proxied the real Microsoft sign-in: her password went through, she approved a genuine Authenticator push, and the proxy kept the session. Seventy minutes later the attacker redeemed it for a token issued to the first-party "Microsoft Outlook" application, scoped to Exchange Online and Microsoft Graph. The first attempt failed with error 50199 (user confirmation required); the retry six seconds later, from another address, succeeded with MFA "satisfied by claim in the token".
That phrase is the whole problem. A refresh token derived from an MFA-satisfied session carries the claim with it, so every later token request presents it, the Conditional Access MFA policy reports Success, and Identity Protection sees a user who keeps passing MFA. In the sample the geofence block policies never applied to those sign-ins either. Revoking sessions kills the refresh token; an access token already issued lives until it expires (about an hour, per Microsoft's documentation at the time of writing) unless Continuous Access Evaluation is in effect. Why MFA is not enough anymore and our Conditional Access design patterns cover the theory.
The five tells in the sign-in logs
- The same app, a new address every time. In the non-interactive log, "Microsoft Outlook" obtaining tokens for Exchange Online about three times a day, each from a different IP address: 95 refreshes from 88 addresses in four weeks, none repeating, in consumer-ISP ranges across a dozen states, none in the user's known networks.
- MFA satisfied by a claim, never performed. Every replayed request shows "MFA requirement satisfied by claim in the token"; the user's own sign-ins show a real Authenticator interaction.
- A user agent that contradicts the recorded OS. The original AiTM sign-in claimed mobile Safari while Entra recorded Windows 10: the proxy relaying the browser, visible on day one.
- Bursts of 50199. Interactive Outlook sign-ins from unknown addresses interrupted with error 50199: replay attempts, on day one and again two days before the mass mailing.
- The cash-out session. Outlook on the web from a hosting-provider address on an unmanaged, non-compliant device, Token Protection Unbound and Continuous Access Evaluation No, followed within the hour by 15 token requests for web sub-services, consistent with exporting the address book.
The baseline makes these visible: the report lists the user's legitimate networks and the three attributes every attacker sign-in shared: MFA by claim, unmanaged device, location outside her home state.
Hunting by export: what to pull from Entra and how to group it
In the Entra admin center, Sign-in logs has tabs for interactive, non-interactive and service principal sign-ins. Per Microsoft's documentation at the time of writing, tenants with Entra ID P1 or P2 keep 30 days in the portal; that is the default retention, and anything longer needs the logs streamed to Microsoft Sentinel or a Log Analytics workspace, or audit data retained through Purview. The sample investigation used that window.
Export both user tabs for the last 30 days. Keep: time (UTC), user, application, resource, IP address, location, autonomous system number, status and error code, the MFA detail, device ID, managed and compliant flags, token protection, continuous access evaluation, user agent and operating system. The non-interactive tab aggregates identical events; keep the count column.
Then group. For non-interactive rows: user, application and resource, then distinct IP addresses and distinct ASNs per day. Three Exchange token refreshes from three addresses in three consumer ISPs on one day is the pattern; refreshes from one carrier ASN are probably a phone. Addresses that only ever appear in the non-interactive log are the ones to explain. Last, list interactive rows with error 50199 by user and address. What could you prove if breached today? explains why the export should exist before the incident.
Hunting by query: a KQL starting point
// Starting point, Microsoft Sentinel: users whose Outlook refreshes for
// Exchange Online come from many distinct addresses in one day.
AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(30d)
| where ResultType == "0"
| where ResourceDisplayName has "Exchange Online"
| where AppDisplayName == "Microsoft Outlook"
| summarize Requests = count(),
DistinctIPs = dcount(IPAddress),
DistinctASNs = dcount(AutonomousSystemNumber),
IPs = make_set(IPAddress, 20)
by UserPrincipalName, Day = bin(TimeGenerated, 1d)
| where DistinctIPs >= 3
| order by DistinctIPs desc, Day desc
Read it as a sketch, not a detection. Three distinct addresses per day is where the sample sat; tune it and exclude the ASNs of your named locations and known carriers. AutonomousSystemNumber separates a rotating residential proxy (many ASNs) from a phone on one carrier.
In Microsoft Defender XDR advanced hunting the equivalent source is the AADSignInEventsBeta table; check its licensing note on Microsoft Learn first. Either way, the next step is a scheduled rule: run it daily, alert on any user above the threshold, and route it to the queue that handles AiTM and stolen-token detections. Defender XDR Incident Readiness builds custom detections like this one and the runbook behind them, $3,500 per project.
The controls that would have changed the outcome
All of this was absent in the sample. Phishing-resistant MFA through a Conditional Access authentication strength: a proxy cannot relay a passkey or a Windows Hello sign-in (our rollout order). Token Protection (sign-in session binding) for Exchange Online and SharePoint on Windows: a bound refresh token is useless on any other device; per Microsoft's documentation at the time of writing it covers a specific set of clients, so check the list first. Continuous Access Evaluation with strict location enforcement, so a revocation reaches access tokens already issued; the second batch in the sample went out on a token that had survived the revocation. A compliant or hybrid-joined device for Outlook and Outlook on the web. Risk-based policies, which need Microsoft Entra ID P2 for the people in scope; Conditional Access, Token Protection and CAE need Microsoft Entra ID P1, included in Business Premium, E3 and E5. A custom detection for distinct addresses per user per day. Separate admin identities.
An AiTM or stolen-token detection is answered with reset, revoke and re-register, never with dismiss; the risky-user alert article has the sequence. Trusted Device and Phishing-Resistant Access puts the device and authentication controls in place without Intune; the Hybrid Identity Attack Path Assessment, $3,950, finds daily accounts holding admin roles; Zero Trust in Microsoft 365 shows the finished state.
Frequently asked questions
Does revoking sessions kill a stolen refresh token?
Yes. Access tokens already issued keep working until they expire unless Continuous Access Evaluation is in effect; in the sample the web session outlived the revocation.
Why did our geofence policy not block it?
In the sample the block policies were recorded as not applied on the token-based sign-ins; the Conditional Access tab of a sign-in names each policy and why. CAE strict location enforcement and a device requirement do reach token requests.
Can we just block the proxy addresses?
Eighty-eight single-use consumer addresses have no reuse value; the pattern is the indicator. ASN-grouped detections and device requirements do what a block list cannot.
Do we need Sentinel for this?
Not for a one-off hunt: export and pivot. You need Sentinel or another retained store for anything older than 30 days and for a scheduled detection; its ingestion and retention are Azure consumption charges billed to you.
Does risk-based Conditional Access catch token replay?
In the sample, no: 95 replayed refreshes produced no detection. Risk policies would have acted on the day-one AiTM detection; the replay needs the hunt above.
Sources
- IT Partner: the redacted sample post-incident report (PDF), /samples/ITPWW540SECOT/post-incident-report-sample-bec-aitm.pdf
- IT Partner blog, content/blog/new/: why-mfa-is-not-enough-anymore-based-on-real-attack-paths, entra-id-conditional-access-real-world-design-patterns, if-your-microsoft-365-tenant-got-breached-today-what-evidence-would-you-have, passkeys-phishing-resistant-mfa-microsoft-365-rollout-order, zero-trust-in-microsoft-365-what-it-actually-looks-like-in-production
- IT Partner service pages, content/services/: ITPWW310SECOT, ITPWW510SECOT, ITPWW520SECOT, ITPWW510CONOT
- IT Partner subscription pages, content/subscriptions/: CFQ7TTC0LFLS__Commercial.json (P1), CFQ7TTC0LFK5__Commercial.json (P2)
- IT Partner engineering notes, September 2026
- Retention, token lifetime, Token Protection coverage and table names per Microsoft's documentation at the time of writing; verify on Microsoft Learn.
| Control | What it would have changed in the sample | Where it lives, and the license |
|---|---|---|
| Phishing-resistant MFA (authentication strength) | The proxy could not have relayed the sign-in; no session to steal | Conditional Access grant control; Entra ID P1 |
| Token Protection (sign-in session binding) | The refresh token would not have worked from the proxy pool | Conditional Access session control; Entra ID P1 |
| Continuous Access Evaluation, strict location | The revocation would have reached the web session before the second batch | Conditional Access session control; Entra ID P1 |
| Risk-based policies (High sign-in risk, High user risk) | The day-one AiTM detection would have blocked or forced a secure password change | Conditional Access with Identity Protection; Entra ID P2 |
| Custom detection: distinct addresses per user per day | The four-week replay would have alerted within days | Sentinel rule or Defender XDR custom detection |
| Playbook: reset, revoke, re-register on AiTM or stolen-token detections | The compromise ends on day one, not day 28 | Your help desk and your provider's runbook |
Key takeaways
- A replayed refresh token lives in the non-interactive sign-in log: the same app fetching Exchange tokens from a new consumer-ISP address every time, MFA satisfied by a claim.
- The day-one tells are in the interactive log: a user agent that contradicts the recorded operating system, and error 50199 on replay attempts.
- Export 30 days of both sign-in tabs and group by user, app, IP address, ASN and MFA result; distinct addresses per user per day is the number.
- The KQL sketch counts distinct addresses and ASNs per user per day for Outlook refreshes; tune the threshold and make it a scheduled rule.
- Phishing-resistant MFA, Token Protection, CAE strict enforcement, device requirements and risk policies each would have ended the sample earlier.
Trusted Device and Phishing-Resistant Access restricts Microsoft 365 to your approved Entra-joined Windows PCs and adds passkeys or Windows Hello for Business, with a report-only pilot first; ask for a quote. If you already have Conditional Access, Conditional Access Policy Review and Break-Glass Validation, a $2,450 fixed-price two-week review, finds where a token-based sign-in would slip past it, and the Plan Optimizer prices Entra ID P1 and P2 as add-ons for the users the risk policies must cover.
Questions this article didn’t answer?
Thirty minutes with Mike — our CEO, not a sales rep. Bring the hard version of the question.