What a Primary Refresh Token Is and Why Attackers Want It

A Primary Refresh Token (PRT) is a long-lived cryptographic artefact issued by Microsoft Entra ID to registered Windows devices after a user authenticates. Once issued, it functions as a master session credential: Entra ID uses it to issue access tokens and refresh tokens for any cloud application without prompting for credentials or MFA. From an attacker’s perspective, stealing a PRT is functionally equivalent to owning the user’s identity — with no further authentication required.

PRTs live in the Local Security Authority process (lsass.exe on Windows) and in the CloudAP key storage provider, protected by the device’s TPM on modern hardware. On devices without TPM binding, they are accessible to a process running as SYSTEM or via certain userland abuse paths.

Storm-2372, a threat actor tracked by Microsoft throughout 2025, ran a large-scale PRT theft campaign targeting government, defence, and NGO organisations. The campaign combined device code phishing to register attacker-controlled devices, PRT harvesting using the RequestAADRefreshToken technique, and lateral movement across the Microsoft 365 tenancy. It is the clearest documented case of PRT theft at operational scale.

Attack Paths

RequestAADRefreshToken / ROADtoken

The most documented PRT theft tool is ROADtoken (part of the AADInternals and ROAD toolset). Running on a device as an interactive user, it calls the internal Windows API RequestAADRefreshToken to extract the device’s PRT directly from CloudAP. No elevated privileges required beyond normal user access. The extracted PRT can then be used from any other machine via the ROADtools library to request access tokens.

ROADtoken.exe -e

The MITRE technique is T1528 (Steal Application Access Token) but also intersects with T1557 (Adversary-in-the-Middle) when used in conjunction with device code phishing.

AiTM via EvilGinx / Evilginx3

An alternative path doesn’t steal the PRT itself but achieves the same outcome: capturing session cookies via adversary-in-the-middle phishing proxies. Evilginx3 and similar frameworks sit between the user and the legitimate Entra ID login page, forwarding credentials and MFA proofs to the real service while capturing the resulting session cookies. The attacker replays these cookies from their own browser, bypassing MFA entirely.

This technique generates a different detection signal from direct PRT extraction and requires separate hunting queries.

Pass-the-PRT

Once an attacker has a PRT (extracted or from a compromised device), they inject it into a new session using the PrtUpdateCookie mechanism or via BrowserCore. Tools like AADInternals’s Join-AADIntDeviceToAzureAD and Get-AADIntAccessTokenForIntuneMDM are commonly used here.

Sigma Rule: ROADtoken PRT Extraction

title: ROADtoken PRT Extraction via RequestAADRefreshToken
id: a1c9e2d4-f3b8-4e7a-9c5d-1e2f3a4b5c6d
status: experimental
description: Detects invocation of the RequestAADRefreshToken API, commonly used by ROADtoken and similar tools to extract Entra ID Primary Refresh Tokens without elevated privileges.
references:
  - https://o365blog.com/post/prt/
author: SOC Analyst Hub
date: 2026/06/15
tags:
  - attack.credential_access
  - attack.t1528
logsource:
  category: process_creation
  product: windows
detection:
  selection:
    Image|endswith:
      - '\ROADtoken.exe'
    CommandLine|contains:
      - 'RequestAADRefreshToken'
  selection_generic:
    CommandLine|contains|all:
      - 'RequestAADRefreshToken'
      - 'CloudAP'
  condition: selection or selection_generic
falsepositives:
  - Microsoft diagnostic tooling (rare)
level: high

KQL: Entra ID Sign-in Anomalies After PRT Abuse

This query hunts for sign-in events from unfamiliar device IDs after a period of normal activity — a key signal when a stolen PRT is used from a new device.

let lookback = 14d;
let recent = 1d;
// Establish baseline device IDs per user
let baselineDevices = SigninLogs
| where TimeGenerated between (ago(lookback) .. ago(recent))
| where ResultType == 0
| summarize BaselineDevices = make_set(DeviceDetail.deviceId) by UserPrincipalName;
// Find sign-ins from devices not in baseline
SigninLogs
| where TimeGenerated > ago(recent)
| where ResultType == 0
| join kind=leftouter baselineDevices on UserPrincipalName
| where not(DeviceDetail.deviceId in (BaselineDevices))
| where isnotempty(DeviceDetail.deviceId)
| extend
    DeviceId = tostring(DeviceDetail.deviceId),
    DeviceOS = tostring(DeviceDetail.operatingSystem),
    IsCompliant = tostring(DeviceDetail.isCompliant),
    Location = strcat(LocationDetails.city, ", ", LocationDetails.countryOrRegion)
| project
    TimeGenerated,
    UserPrincipalName,
    DeviceId,
    DeviceOS,
    IsCompliant,
    AppDisplayName,
    IPAddress,
    Location,
    AuthenticationRequirement,
    ConditionalAccessStatus
| sort by TimeGenerated desc

A high-confidence refinement: filter for AuthenticationRequirement == "singleFactorAuthentication" on accounts that previously always required MFA — this directly surfaces MFA bypass via PRT replay.

After AiTM phishing, attacker-controlled sessions typically originate from different IP geographies than the victim’s normal access patterns.

SigninLogs
| where TimeGenerated > ago(7d)
| where ResultType == 0
| summarize
    SessionCount = count(),
    IPList = make_set(IPAddress),
    Countries = make_set(LocationDetails.countryOrRegion),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated)
    by UserPrincipalName, CorrelationId
| where array_length(Countries) > 1
| where SessionCount < 5
| extend SuspiciousGeoHop = array_length(Countries) >= 2
| where SuspiciousGeoHop == true
| project
    UserPrincipalName,
    CorrelationId,
    IPList,
    Countries,
    FirstSeen,
    LastSeen
| sort by FirstSeen desc

For higher fidelity, cross-reference with AADUserRiskEvents for unfamiliarFeatures or tokenIssuerAnomaly risk detections, which Entra ID Protection generates for suspicious PRT usage.

KQL: Device Code Authentication Abuse

Device code phishing precedes many PRT theft campaigns. Hunt for device code flows from users who do not normally use them.

SigninLogs
| where TimeGenerated > ago(30d)
| where AuthenticationProtocol == "deviceCode"
| summarize
    DeviceCodeAttempts = count(),
    IPList = make_set(IPAddress),
    AppList = make_set(AppDisplayName),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated)
    by UserPrincipalName
| where DeviceCodeAttempts > 0
| sort by DeviceCodeAttempts desc
// Cross-reference with your list of privileged accounts and service accounts

Flag service accounts and privileged roles using device code authentication — there is no legitimate reason for these accounts to authenticate interactively via device code flows.

KQL: AADInternals / ROADtools Service Principal Activity

Tool-specific hunting: AADInternals and ROADtools access Graph API endpoints that legitimate applications do not use in the same combination.

MicrosoftGraphActivityLogs
| where TimeGenerated > ago(7d)
| where RequestUri has_any (
    "oauth2/devicecode",
    "oauth2/token",
    "/me/registeredDevices",
    "/devices?",
    "joinedTeams"
)
| where UserAgent has_any (
    "python-requests",
    "AADInternals",
    "roadtools",
    "Go-http-client"
)
| project TimeGenerated, UserAgent, RequestUri, IPAddress, UserId
| sort by TimeGenerated desc

Key Investigation Steps When PRT Theft Is Suspected

  1. Identify the compromised device. Check DeviceDetail.deviceId in sign-in logs. If the PRT was stolen and replayed, a new unregistered device ID will appear. Compare against Intune/Entra registered device list.

  2. Review authentication methods. Pull AuthenticationDetails from the sign-in record. Legitimate PRT-based authentication shows primaryAuthenticationMethod: PrimaryRefreshToken. If a PRT was used from a different device, the device compliance state will often show as non-compliant or not evaluated.

  3. Revoke sessions immediately. Use Revoke-MgUserSignInSession (Graph PowerShell) or the Entra admin portal to revoke all refresh tokens for the affected user. Note that this revokes existing sessions but does not prevent re-issuance if the device itself is still compromised.

  4. Rotate device certificate. On the originating device, use dsregcmd /leave and re-register to force a new PRT to be issued, invalidating the stolen token.

  5. Check for new registered apps and OAuth consents. Attackers who have obtained persistent access via PRT often register OAuth applications or grant consent to existing apps to maintain access after token revocation.

Detection Coverage Assessment

Attack StepDetection SignalCoverage
Device code phishingSigninLogs — deviceCode protocolMedium
ROADtoken executionProcess creation — ROADtoken.exeHigh (if EDR deployed)
PRT extracted and replayedNew device ID in sign-in logsMedium
AiTM cookie captureGeo-impossible sessionsMedium
Post-access app registrationAuditLogs — application consentHigh

TPM-backed PRT binding (available on Entra-joined devices with TPM 2.0 and token protection Conditional Access policy) prevents direct PRT extraction. Enabling the Conditional Access token protection policy for Exchange Online, SharePoint, and Teams is the most effective preventive control, though as of mid-2026 it requires devices to be Entra-joined and TPM-capable.