The device code flow was built for TVs and CLI tools that can’t open a browser. Attackers figured out it also works great for stealing a session without ever seeing a password.
Why this matters now
Over the past two years, phishing crews have shifted away from fake login pages. Login pages get flagged by browser isolation, password managers refuse to autofill on the wrong domain, and MFA prompts stop most credential replays cold. Device code phishing sidesteps all of it.
The trick uses a legitimate OAuth flow — the device authorization grant (RFC 8628) — the same mechanism Microsoft 365, Google Workspace, GitHub CLI, and countless SaaS tools use to let a smart TV or headless server ask a user to authenticate on a second screen. The victim isn’t asked to type a password anywhere suspicious. They’re asked to visit a real, legitimate login page and enter a short code. Because the domain is genuine and MFA completes normally, the attack looks like a routine login from the identity provider’s point of view.
The short version
| Point | Why it matters |
|---|---|
| No fake login page | Standard phishing filters and browser isolation don’t trigger |
| MFA completes normally | The victim approves a real prompt, so MFA bypass detections miss it |
| Token, not password, is stolen | The attacker walks away with a valid access and refresh token, often long-lived |
| Detection lives in identity logs, not email | Your best signal is the token grant event, not the phishing email itself |
How the attack actually runs
- The attacker starts a device authorization request against the target’s real identity provider (Microsoft Entra ID, Okta, Google, etc.) and gets back a
device_codeand a shortuser_code. - The attacker sends the victim a message — Teams, email, SMS — telling them to go to a real, well-known verification URL and enter the code. Pretext examples seen in the wild: “join this Teams meeting,” “verify your device for the new VPN policy,” “authenticate to view this shared document.”
- The victim, on the legitimate identity provider domain, enters the code and completes MFA as usual.
- The identity provider issues an access token and refresh token to the attacker’s polling client, not the victim’s browser.
- The attacker now holds a valid session — often with a refresh token lifetime measured in weeks — usable against mail, file storage, and any downstream app trusting that identity.
Nothing here is a vulnerability in the OAuth spec. It’s a phishing pretext wrapped around a legitimate flow, which is exactly why it slips past controls built to catch fake domains and credential harvesting pages.
What to look for in your logs
The device code flow leaves a distinct fingerprint in identity provider sign-in logs, even though the sign-in itself looks “successful.” The fields to pull out:
event_type: sign-in / token-issued
grant_type: device_code
client_app: the polling application that requested the code
client_id: often a well-known first-party client ID (e.g. a CLI or Teams client)
user_agent: frequently absent or generic (headless polling client)
source_ip / geo: the *requesting* client's IP, often unrelated to the victim's usual location
session_lifetime: long-lived refresh token issued
The single highest-signal check: correlate the IP that requested the device code against the IP the user authenticated from. In a legitimate device flow (a smart TV, a CLI tool on a known corporate laptop), those IPs are close together on the network or at least consistent with the user’s normal pattern. In a phishing case, the requesting IP is the attacker’s infrastructure and the authenticating IP is the victim’s — two unrelated locations completing one login.
Pseudo-code for a detection rule
rule: oauth_device_code_anomalous_grant
when:
event.grant_type == "device_code"
and event.result == "success"
correlate:
requesting_ip = event.device_code_request.source_ip
auth_ip = event.mfa_completion.source_ip
flag when:
geo_distance(requesting_ip, auth_ip) > threshold_km
or requesting_ip in known_bad_asn_list
or client_app not in allowlisted_device_flow_clients
severity: high
mitre:
- T1528 # Steal Application Access Token
- T1566 # Phishing
Run this as a near-real-time rule against your identity provider stream where possible — the useful response window is the token’s lifetime, not the next day’s log review. If your identity provider doesn’t expose device-code-specific fields directly, you can often approximate grant_type from the sign-in log’s authentication method field, which is worth confirming against a real test login before you trust the rule.
Hardening beyond detection
Detection buys you time, but the durable fix is reducing how often this flow is even reachable:
| Control | Effect |
|---|---|
| Disable device code flow for the tenant unless a specific app needs it | Removes the attack surface entirely for most orgs |
| Allowlist device flow to specific known client IDs | Blocks unknown polling clients from ever completing the grant |
| Shorten refresh token lifetime for device-flow sessions | Limits the blast radius if a grant does succeed |
| Conditional access requiring compliant/managed device for token use | Stops the stolen token from being usable on attacker hardware |
Most identity providers ship at least the first two controls today. If your organization has no legitimate use case for device code sign-in — no smart TVs, no CLI tools relying on it — turning it off is the single highest-leverage change you can make this week.
Final thought
Device code phishing is a good reminder that “MFA completed successfully” and “the legitimate identity provider domain was used” are necessary conditions for a safe login, not sufficient ones. The grant type and the correlation between requesting and authenticating IPs matter just as much. If you’re not already alerting on device code grants, it’s a cheap rule to add and one of the higher-signal detections you can put in front of an identity-based attack.
If you want help reviewing whether your identity provider logs give you enough visibility to catch this, contact us.