Device code phishing: the page is real, and that is the problem
There is no lookalike domain. There is no fake login form. The victim types a code into the genuine Microsoft page, approves with their genuine passkey, and hands an attacker a working session. Every control built to spot a fake page is looking in the wrong place, because there is no fake page to spot.
The meeting invite that was not a meeting invite
Picture a Thursday afternoon at a mid-size engineering firm. A project manager gets a Teams message from someone who appears to be a partner at an architecture practice they genuinely work with. There is a short pleasantry, a reference to a real project, and then a line about a shared document workspace: the portal is asking me to add you, can you approve on your end, code is BQTX-9DRM.
The project manager has done this before with other tools. She opens the link, which goes to microsoft.com/devicelogin. It is not a homoglyph domain. It is not a redirect through some tracking service. It is the actual Microsoft page, served over TLS, with a valid certificate issued to Microsoft, showing the layout she has seen a hundred times.
She types the code. Microsoft asks her to sign in, which she does with her passkey, the good kind, the phishing-resistant kind that her security team spent eighteen months rolling out. Face ID, one tap, done. A consent screen appears naming an application. She clicks through it the way everyone clicks through it. The page says she is all set and she can close the window.
She closes the window. Nothing looks wrong, because nothing looked wrong. The attacker, sitting on a virtual machine in another country, now has an access token and a refresh token for her account. He did not steal her password, because he never asked for it. He did not defeat her passkey, because he never touched it. He did not stand up a fake page, because he did not need one. He asked her to authorise a session, and she did, and the session was his.
Short answer: Device code phishing tricks a user into approving an OAuth device authorization session that the attacker started on the attacker's own machine. Because the user authenticates on the genuine provider page, there is no fake domain to block and no credential to steal, so MFA and passkeys are satisfied normally. Only a consent that is displayed and cryptographically bound to the device that started the flow stops it.
What is device code phishing?
Device code phishing is the abuse of a legitimate and genuinely useful part of OAuth 2.0: the device authorization grant, specified in RFC 8628. The attack does not exploit a bug in that specification. It exploits the fact that the specification, correctly and by design, decouples the device that wants access from the device that grants it.
The consequence is subtle enough that many experienced defenders have read about this attack twice and still not seen the shape of it. So it is worth building up slowly, starting from why the flow exists at all.
The television problem
Imagine you have just bought a streaming device and you want to sign in to a video service. The device has a screen but no usable keyboard. Typing an email address with a directional pad is unpleasant. Typing a strong password that way is miserable. Typing a one-time code from an authenticator app into a television, before it expires, while your family watches, is a small tragedy.
So the industry solved it properly. The television does not ask you to log in. It shows you a short code and a URL. You pick up the device already in your hand, the one with a keyboard and a password manager and a fingerprint sensor, you go to that URL, you enter the code, and you approve. The television polls in the background, notices it has been approved, and receives its tokens. You never typed a password into the television, which is good, because televisions are not where you want your credentials.
This is a good design. It moved the sensitive part of the ceremony from a bad input device to a good one. The same flow is now used for command line tools, for CI runners, for printers, for conference room hardware, for IoT devices, and for the Azure and AWS command line utilities that engineers use every day. If you have ever run a cloud CLI command and had it print a code and open a browser tab, you have used it.
The inversion
Now notice what the flow assumes. It assumes that the person entering the code is the person who is sitting in front of the device that generated it. That assumption is entirely reasonable in the living room. You can see the television. The code is on your own screen, in your own home. There is no ambiguity about which device you are authorising, because there is only one candidate device and it is three metres away.
The protocol does not enforce that assumption. It cannot. The whole point is that the two devices are separate and may not be able to communicate directly. So the code is just a code. Whoever holds it can put it in front of a user, and whoever the user approves it for, gets the tokens.
The attack is exactly that inversion, and nothing more. The attacker starts a device authorization flow on his own machine, receives the user code from the provider, wraps it in a plausible story, and gets a real user to enter it on the real page. The user approves what they believe is their own action. What they are actually approving is a session belonging to a machine they have never seen, in a country they have never visited, operated by a person whose name they do not know.
How does the attack work, step by step?
Here is the full sequence, written out slowly, because the individual steps are each unremarkable and that is precisely what makes the whole convincing.
One. The attacker sends a request to the provider's device authorization endpoint, identifying a client application. Frequently this is a well known, first party, pre-authorised client identifier, because those tend to have broad scopes and tend not to look unusual in logs.
Two. The provider responds with a device code, which the attacker's machine keeps, and a user code, which is the short human readable string, along with the verification URL. The user code typically lives for a limited window, often around fifteen minutes.
Three. The attacker delivers the user code to the victim with a pretext. Reporting on real campaigns has described lures arriving through Teams messages and meeting invitations, through messaging applications, and through email, often from an already compromised account belonging to someone the victim trusts.
Four. The victim opens the genuine verification page. There is nothing to detect here. The domain is correct. The certificate is correct. A link scanner following the URL finds the real Microsoft or Google endpoint, because it is the real endpoint.
Five. The victim authenticates. If the tenant requires multi-factor authentication, the victim performs it. If the tenant has deployed passkeys, the victim uses their passkey. Every one of these controls functions exactly as designed. The victim is who they say they are. The provider is right to let them in.
Six. The victim approves the consent prompt, which names an application. The application name is chosen by the attacker in the sense that the attacker chose which client identifier to request. The prompt does not say this session will be granted to a machine at an address you have never used, in a location you have never signed in from, because the prompt has no way of knowing that would matter.
Seven. The attacker's machine, which has been politely polling the token endpoint, receives an access token and, crucially, a refresh token. The refresh token is the prize. Access tokens expire in an hour or so. Refresh tokens can persist for a long time, which is why the next post in this series is about how much authority survives a password reset.
Eight. The attacker now reads mail, enumerates files, creates inbox rules, and in many documented cases establishes additional persistence so that the initial session no longer matters.
Why is there nothing for a phishing filter to catch?
This is the sentence that reorganises the reader's mental model, so it deserves its own section: there is no phishing page.
Almost every anti-phishing control in existence is built on the premise that the attacker must, at some point, get the victim to interact with infrastructure the attacker controls. That premise underpins domain reputation, homoglyph detection, certificate transparency monitoring, brand impersonation detection, URL rewriting, sandbox detonation, and the entire discipline of teaching users to check the address bar.
Device code phishing does not require the attacker to host anything. The victim's browser only ever contacts the legitimate identity provider. The credential is never transmitted anywhere except to the party that should receive it. The TLS session is with Microsoft or Google. If you trained a user to hover over links and read domains carefully, you trained them to pass this attack with confidence, because the domain really is right and their diligence returns a green light.
Compare this with adversary in the middle phishing, which we covered in the post on session theft. There, the attacker runs a reverse proxy and the victim's traffic flows through attacker infrastructure. That is detectable in principle: the domain is not the real one, the proxy has fingerprints, and the session token ends up in an unusual place. Device code phishing removes even that foothold. There is no proxy. There is no attacker domain in the victim's browser history. The forensic artifact on the victim side is a perfectly ordinary successful sign-in.
Does MFA stop device code phishing?
No, and it is worth being precise about why, because the reason is more interesting than a simple no.
Multi-factor authentication answers the question is this the right person. In a device code attack, the answer is genuinely yes. The right person really is at the keyboard. They really did present their second factor. The identity provider's assertion is completely accurate. Nothing has been bypassed, spoofed, or replayed.
The same is true of passkeys, and this is the part that surprises people who have invested heavily in a passwordless programme. Passkeys are phishing resistant in a specific, valuable, and narrow sense: the credential is scoped to an origin, so it cannot be replayed against a lookalike domain, which kills credential phishing as a category. That is a real achievement and organisations should still deploy them.
But a passkey protects the login ceremony. Device code phishing does not attack the login ceremony. It lets the login ceremony succeed, honestly and completely, and attacks the authorisation decision that happens afterwards. The user proves who they are, flawlessly, and then approves the wrong thing. This is the same structural gap we examined in passkeys prove the login, not the transaction, showing up at the consent step instead of the payment step.
An organisation can be one hundred percent enrolled in phishing-resistant authentication and still lose a tenant to one approved code. Phishing-resistant authentication is not phishing-resistant authorisation. Those are different problems and the industry has solved only the first one.
How common is this attack now?
Common enough that it moved from state-aligned espionage to commodity crime in roughly a year, which is the usual trajectory for a technique that works.
Microsoft publicly described a device code phishing campaign in February 2025 attributed to an actor it tracks as Storm-2372, with lures that included Teams meeting invitations and targeting reported across government, defence, energy and academic sectors. In April 2026, Microsoft published on a further evolution in which the attackers automated the flow so that fresh, live codes could be generated on demand rather than reused, which neutralises the natural mitigation of short code lifetimes: if the code in the lure is always minutes old because it was minted when the victim opened the message, expiry stops helping.
Research notes published by the Cloud Security Alliance in 2026 described a substantial surge in enterprise account takeover through this flow, affecting hundreds of Microsoft 365 organisations. Independent security firms including Huntress and Proofpoint have reported on phishing-as-a-service kits that package the technique for operators with no particular skill. Volexity documented cross-device flow abuse in earlier reporting that predates the mainstream attention.
Treat the specific counts in any of this reporting as directional rather than precise. Vendor and researcher telemetry measures what that vendor can see, campaign attribution is difficult, and there is no census of device code compromises. What the reporting establishes reliably is the shape and the trend: the technique works, it has been industrialised, and the tooling is now available to buy.
The downstream cost is the ordinary cost of a tenant compromise. Mailbox access leads to business email compromise, which the FBI's Internet Crime Complaint Center recorded at 3.05 billion dollars in reported United States losses across 24,768 complaints in 2025 (FBI IC3 2025 Internet Crime Report). For a single organisation, one successful device code compromise of a finance or executive mailbox is an incident response engagement, a forensic review, a notification exercise, and frequently a fraudulent payment.
What did the human actually approve?
Here is the heart of it. Ask the project manager from the opening scene what she approved, an hour afterwards, and she will tell you she approved adding a partner to a document workspace. She will be entirely sincere and completely wrong.
What she actually approved was the issuance of an access token and a refresh token, with a particular set of scopes, to a particular OAuth client, for a session initiated by an unknown machine. Not one element of that sentence was displayed to her in a form she could evaluate. She saw an application name. She did not see the scopes in language that meant anything operational. She did not see the machine. She could not have seen the machine, because the consent screen has no field for it.
This is what we call consent without a display. The user is asked to authorise something, and the thing they are authorising is not rendered in front of them in a form that lets them tell an ordinary action from a catastrophic one. The click is real. The intent behind it is aimed at something else entirely.
Put it in physical terms. Someone hands you a clipboard with a signature line and a single word at the top: Access. You sign, because you were expecting to sign something like that, and the person handing it to you is plausible. What you signed was a transfer of the deed to your house. Your signature is genuine. Your identity was verified. The notary was real. None of that helps, because the document was never rendered.
Where else does this same failure appear?
This is the moment where a taxonomy earns its keep. If you have read the post on blind signing, the paragraph above should feel familiar, because it is the same failure wearing different clothes.
In that story, cryptocurrency exchange signers approved a transaction while looking at an interface that had been tampered with. The signature was mathematically perfect. The signers were the right people. The keys were the right keys. What they saw on the screen was not what they signed, and roughly 1.5 billion dollars left the building. The mechanism there was a compromised front end; the mechanism here is a consent screen with no field for the thing that matters. The structure is identical: the human authorised something they could not see.
We catalogue this pattern in the Identity Failure Map because it recurs across domains that share no vendors, no protocols, and no conferences. Treasury software, cryptocurrency custody, cloud identity, and consumer banking all reproduce it independently, which tells you it is not a bug in any one system. It is a gap in how the industry thinks about approval.
Once you have the pattern, you start seeing it everywhere. A push notification that says Approve sign-in? with no context is the same failure. A payment confirmation that shows a reference number instead of a beneficiary is the same failure. An agent asking proceed? without saying what it is about to do is the same failure, which is why we wrote about approval theatre separately.
What should you do first, before buying anything?
Turn the flow off where you do not need it. This costs nothing, requires no vendor, and is the correct first action. Any article that recommends a product before recommending the free configuration change is selling rather than helping.
In Microsoft Entra ID, Conditional Access includes an authentication flows control that lets you block the device code flow for users, groups, or the whole tenant. Google Workspace administrators have equivalent controls over which OAuth flows and applications may be used. If your organisation has no smart televisions, no shared conference hardware needing sign-in, and engineers who authenticate their command line tools through a browser flow, you may be able to block it outright and be done.
The honest complication, which practitioners raise constantly and which vendors tend to skip, is that many organisations block it, discover that something important broke, and quietly re-enable it. The usual casualties are command line utilities on headless servers, build agents, conference room systems, and legacy hardware. So the realistic policy is not a single switch but a scoped one.
| Approach | What it does | Where it breaks |
|---|---|---|
| Block device code flow tenant wide | Removes the attack surface completely | Headless devices, CLI tools, conference hardware stop working |
| Block by default, allow for a named group | Keeps the flow for the few accounts that need it | Requires knowing which accounts need it, which most tenants do not |
| Allow only from compliant or managed devices | Narrows who can complete the approval | The victim's device usually is compliant, so it may not stop the attack |
| Shorten token lifetimes and enforce re-auth | Reduces dwell time after compromise | Does not prevent the initial grant, and refresh tokens may persist |
| User training on codes | Some users will pause | The page is genuine, so diligent users are reassured rather than warned |
| Displayed, signed consent | Binds approval to what was shown and to the initiating device | Requires provider support for native flows, which does not exist yet |
What would actually close the gap?
The structural fix follows directly from the diagnosis. If the failure is that the human approved something they could not see, the fix is to show them, on a device the attacker does not control, and to make the approval a signature over exactly what was shown.
Concretely, that means the consent request is rendered as a canonical object, pushed to the user's enrolled companion device, displayed there independently of whatever page the desktop browser is showing, and signed with the user's key. The signature covers the canonical bytes, so the thing approved and the thing displayed cannot diverge.
Here is what such a payload looks like. Note that it contains the fields the current consent screen omits, which is the entire point:
{
"typ": "consent.request",
"client_id": "1950a258-227b-4e31-a9cf-717495945fc2",
"client_name": "Contoso Document Sync",
"publisher_verified": false,
"scopes": ["Mail.Read", "Files.ReadWrite.All", "offline_access"],
"initiating_device": {
"flow": "urn:ietf:params:oauth:grant-type:device_code",
"first_seen_ip_asn": "AS200000",
"approx_location": "Frankfurt, DE",
"device_seen_before": false
},
"grant_ttl_days": 90,
"requested_at": "2026-09-15T14:22:09Z"
}
The user's phone renders that as three plain sentences: an unverified application called Contoso Document Sync wants to read your mail and read and write all your files, for ninety days, for a machine in Frankfurt that you have never approved before. Then it asks for a fingerprint.
The project manager in the opening scene does not approve that. Nobody approves that. The attack does not fail because she became more suspicious; it fails because for the first time she was shown the thing she was being asked about.
Verification is the ordinary signature check, done by the authorisation server before it issues tokens:
canonical = canonicalize(consent_request) # deterministic bytes
digest = sha256(canonical)
assert receipt.payload_digest == digest # signed what was shown
assert verify_ed25519(receipt.sig, digest, pubkey) # by the enrolled human
assert receipt.device_binding == flow.device_code # for THIS session
assert now() - receipt.signed_at < 120 # freshly, not replayed
issue_tokens()
The third assertion is the one that kills this specific attack. The receipt is bound to the device code of the session that requested it. A signature produced for one session cannot be transplanted onto another, so an attacker cannot induce an approval for a session the user thought was their own.
The artifact that comes out the other end has independent value. A security operations team looking at a suspicious grant six months later does not have to reason about a log line saying a sign-in succeeded. They have a receipt showing which human approved which scopes for which machine, verifiable offline against a published key. That is the difference between an audit trail you have to trust and evidence you can check, and it is the same property we describe in the authority graph.
Honest limits
Several, and they matter.
Microsoft and Google own the flow. Native device code consent lives inside the identity provider. Nobody outside can insert a signature requirement into it without the provider exposing a hook. That is a real dependency, and it means the control described above is not something you can switch on for Entra ID today. What can be done today is wrapping consent for third party applications that integrate a signing step themselves, and for tenants that route high value application authorisation through a partner identity provider. Manav has not shipped native device code integration with Microsoft or Google, and would need their cooperation to.
A determined user can still approve a bad thing. Rendering the request improves the odds substantially, because most victims of this attack would refuse if they understood the ask. It does not make them immune. Someone in a hurry, under pressure from a convincing pretext, may read the phone screen and tap anyway. Signatures bind intent, not judgment.
Attackers will move. If device code consent becomes hard, the same operators will spend more time on ordinary illicit consent grants, on session theft, and on help desk social engineering. That is not a reason to leave this open, but it is a reason to be suspicious of any claim that one control ends account takeover.
The companion device is not magic. If the user's phone is compromised, the rendering can lie there too. Separating the display from the initiating machine raises the attacker's cost significantly; it does not create an uncompromisable surface. There is no such thing.
Proximity approaches may get there first. The IETF has active work on cross-device flow security, and proximity checks using local transports are a plausible mitigation for some of these flows. If that standardises and ships broadly, it addresses a good portion of this attack class without any of the machinery above. That would be a fine outcome.
What to do this week
- Find out whether the device code flow is enabled in your tenant. Most administrators do not know. In Entra ID this is visible through Conditional Access authentication flows policy; check rather than assume.
- Search your sign-in logs for the device code flow specifically. Filter on the authentication protocol. In a typical corporate tenant the legitimate volume is small and identifiable, which tells you what you would break by blocking it.
- Block it for everyone who does not appear in that list. Start with executives, finance, and anyone with privileged roles. This is the highest value hour of work in this article.
- Write down the exceptions and why they exist. The reason organisations re-enable the flow tenant wide is that nobody recorded which three systems needed it. A list prevents the panic revert.
- Alert on device code grants from unfamiliar locations or to unusual clients. This is detection and it will not catch everything, but the volume is low enough that the alert is workable rather than noise.
- Update the security awareness message. The specific instruction is narrow and memorable: never enter a code that somebody else sent you, no matter how real the page looks. Explain that the page being real is the trick.
- Audit existing OAuth grants while you are in there. A device code compromise usually leaves a grant behind, and grants outlive password resets. That is the subject of the companion post on consent persistence.
- Ask your identity provider when consent will show device context. Vendor roadmaps move when customers ask in support tickets. This is a reasonable thing to ask for and nobody is asking.
If you want to see what a displayed, device bound approval feels like as a user rather than as a diagram, we keep a working demonstration at /labs/sign/, and the integration details are in the developer documentation.
Frequently asked questions
What is device code phishing? It is an attack in which a criminal starts an OAuth device authorization session on their own machine, then persuades a victim to enter the resulting code on the genuine identity provider page. The victim authenticates normally and approves, and the tokens are issued to the attacker's machine. No fake website and no stolen password are involved.
Does MFA stop device code phishing? No. Multi-factor authentication confirms that the correct person is present, and in this attack the correct person genuinely is present and genuinely does approve. The provider's identity assertion is accurate. What fails is the authorisation decision that follows, which no authentication factor evaluates.
Do passkeys stop device code phishing? No, for the same reason. Passkeys are bound to an origin, which defeats credential phishing on lookalike domains. Device code phishing uses the real origin, so the passkey works perfectly and the attack proceeds. Passkeys remain worth deploying; they simply address a different problem.
Should I just block the device code flow entirely? Usually yes, for most users, and it should be your first action because it is free. Check your sign-in logs first to see which accounts legitimately use it, typically engineers with command line tools and shared conference hardware, then block it for everyone else and record the exceptions.
How do I know if we have already been hit? Look for successful sign-ins using the device code flow from unfamiliar locations or autonomous systems, then look for OAuth grants and inbox rules created shortly afterwards. The initial sign-in looks legitimate, so the persistence artifacts are usually the stronger signal.
Why do attackers prefer this over ordinary phishing? Because it defeats the entire category of controls built around detecting attacker infrastructure. There is no domain to blocklist, no page to sandbox, and no credential to be caught in transit. It also satisfies multi-factor requirements without any bypass technique, so it works against well defended organisations.
What would actually prevent it? Consent that is rendered on a device the attacker does not control and signed cryptographically, with the signature bound to the specific session being authorised. That way an approval cannot be transplanted from the user's intended action onto the attacker's session, and the user sees the scopes and the requesting machine before deciding.
Sources
- IETF, RFC 8628: OAuth 2.0 Device Authorization Grant, the specification the attack abuses.
- IETF OAuth Working Group, cross-device flow security drafts and working group documents, ongoing work on this attack class.
- Microsoft Security Blog, reporting on Storm-2372 device code phishing (February 2025) and on automated device code campaigns (April 2026).
- Microsoft Learn, Conditional Access documentation, including the authentication flows control used to block the device code flow.
- Cloud Security Alliance, research notes on OAuth device code phishing against Microsoft 365 organisations, 2026.
- Volexity, research on cross-device authentication flow abuse.
- Huntress, reporting on device code phishing and phishing-as-a-service kits.
- FBI Internet Crime Complaint Center, 2025 Internet Crime Report, for downstream business email compromise losses.
- NIST, Special Publication 800-63B, Digital Identity Guidelines: Authentication and Lifecycle Management.
Phishing-resistant authentication is not phishing-resistant authorisation. You can prove exactly who someone is and still let them approve something they never saw.