If you work the SOC queue at a Microsoft 365 shop, device code phishing is one you should be able to recognize on sight. It’s been showing up across the industry for almost two years now. Storm-2372 has been running it since August 2024. APT29 has been attributed alongside it. In early 2026, Huntress reported a campaign that hit more than 340 Microsoft 365 tenants in five countries.

It keeps working because it’s sneaky. The user lands on the real Microsoft login page. The MFA prompt is real. The cert is valid. There’s no malware to find on the endpoint. And the tokens the attacker walks away with keep working even after the user resets their password, because OAuth refresh tokens are independent of the password and stay valid until they’re explicitly revoked or expire by policy.

This post walks through what the device code flow actually is, how the attack abuses it, what it looks like in your logs, and what to do when you spot it. The technical context up front matters because once you understand what the flow is supposed to do, the abuse pattern is obvious.

What device code flow is and why it exists

The “device code flow” is a sign-in method built into Microsoft Entra ID and most other identity systems. It exists for one specific problem: how do you sign into something on a device that has no good way to handle a normal login? Smart TVs, IoT gadgets, conference room systems, command-line tools. Stuff with no keyboard, or no browser, or both. Typing a password and a six-digit MFA code into a Roku remote is brutal, so the flow lets that device hand off the actual sign-in to your phone or laptop instead.

Here’s how it’s supposed to go:

  1. Your smart TV (or whatever device is signing in) asks Entra ID for a sign-in code.
  2. Entra ID hands back a short user code, like ABCD-EFGH, and a verification URL: microsoft.com/devicelogin.
  3. The TV shows you both on screen, then sits there waiting.
  4. You go to that URL on your phone or laptop, type in the code, and sign in normally with your password and MFA.
  5. Entra ID marks the code as approved.
  6. The TV gets back a session token. You’re signed in.

This is fine. It’s how Azure CLI works. It’s how GitHub CLI works. The Microsoft Authentication Broker (a built-in Microsoft app with the ID 29d9ed98-a469-4536-ade2-f981bc1d605e) uses it for legitimate device-join scenarios. There’s nothing wrong with the design.

Here’s the catch though. Nothing in the flow ties the device that asked for the code to the user who entered it. They’re just two different things, and Entra ID assumes they’re cooperating. If they aren’t, if the device that asked is the attacker’s and the user entering it is the victim, Entra ID has no way to know.

That’s the gap the attack relies on.

The attack in three steps

Once you see the gap, the attack is genuinely simple:

  1. The attacker asks Microsoft for a sign-in code. They use the same legitimate device code flow you just read about. Microsoft hands them a real code, valid for about 15 minutes.
  2. The attacker tricks the user into typing the code on the real Microsoft page. They send it over email, Teams, WhatsApp, Signal, whatever. The pitch is usually “go to microsoft.com/devicelogin and enter this code to join the meeting” or “to view this document.” The user goes to the real page, enters the code, signs in with their actual password and MFA.
  3. The attacker gets the user’s session. Microsoft hands the access and refresh tokens back to the attacker, not the user. The user has no idea anything happened.

That’s the whole attack. No malware. No fake login page. No lookalike domain. The attacker uses Microsoft’s own infrastructure to phish your users.

One thing worth knowing as you read the rest of this post. As of early 2026, the most advanced campaigns don’t even hand the user a pre-generated code. Microsoft Defender Security Research published findings in April 2026 on campaigns that generate the code on the fly the moment the victim clicks the phishing link, sidestepping the 15-minute validity window entirely. The detections below still apply because the protocol fingerprint doesn’t change, but the operational tempo is faster than the basic attack chain implies.

Why it’s hard to spot from the user side

Everything we train users to check, this attack passes:

  • The URL is microsoft.com/devicelogin. It’s the real Microsoft.
  • The cert is valid because it’s Microsoft.
  • The MFA prompt is the user’s normal prompt because it’s the real Entra ID flow.
  • The login page is the same one they sign into every day.

The only thing the user has to do “wrong” is type a code that someone sent them. That’s a tough one to defend through training, especially when the code shows up in a Teams message from a coworker whose account got taken over yesterday. So the catch happens in the logs, not at the user.

What it looks like in your queue

The technical fingerprint is one specific value in the sign-in logs:

AuthenticationProtocol == "deviceCode"

In a normal corporate environment, almost nobody legitimately uses this. The exceptions are usually developers running Azure CLI, IT folks doing device joins, and a small handful of automation accounts. If a regular knowledge worker shows up with deviceCode in their sign-in logs, that’s a hunting lead at minimum.

Here’s the simple query to see who’s doing this in your tenant over the last 30 days:

union SigninLogs, AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(30d)
| where AuthenticationProtocol == "deviceCode"
| where ResultType == 0
| summarize SignInCount = count(),
            Apps = make_set(AppDisplayName),
            IPs = make_set(IPAddress),
            Countries = make_set(Location)
            by UserPrincipalName
| order by SignInCount desc

Run this. The output is your baseline. Devs and IT will be on it. If anyone else shows up, that’s where you start digging.

For an actual alerting rule, you want to fire on anyone outside that known group:

let known_device_code_users = dynamic([
    // UPNs of devs/IT who legitimately use device code flow
    // Pull these from your 30-day baseline above
]);
union SigninLogs, AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(1h)
| where AuthenticationProtocol == "deviceCode"
| where ResultType == 0
| where UserPrincipalName !in~ (known_device_code_users)
| project TimeGenerated, UserPrincipalName, AppDisplayName, AppId,
          IPAddress, Location, UserAgent, DeviceDetail, CorrelationId

Schedule this hourly with an hour lookback. Hits are usually high-quality. If you’ve never seen one before, the first one will probably be real.

What to do when an alert fires

When a deviceCode alert lands in your queue, work it in this order:

  1. Check the user. Is this a developer or IT person who actually uses Azure CLI, device join, or similar? If yes, add them to your allow-list and close the alert. If no, keep going.
  2. Check the IP. Is it the user’s normal IP range or country? Is it a known VPN, hosting provider, or somewhere they’ve never signed in from? Hosting and PaaS IPs (Railway, DigitalOcean, AWS) on a deviceCode sign-in for a non-developer is a strong signal. Railway in particular has been heavily abused by the EvilTokens PhaaS platform tracked by Huntress since February 2026.
  3. Check the app. The AppDisplayName and AppId fields tell you what the attacker was authenticating to. If you see AppId equal to 29d9ed98-a469-4536-ade2-f981bc1d605e (Microsoft Authentication Broker) on a non-IT user, escalate. That’s the specific path Storm-2372 used to set up further access.
  4. Check for follow-on activity. Look at the same user’s sign-ins, mailbox activity, file access, and AuditLogs for the next hour. If you see new mail forwarding rules, mass downloads from SharePoint or OneDrive, or any device registration events, escalate immediately.
  5. If it’s confirmed bad, contain it. Revoke the user’s sessions (Revoke-MgUserSignInSession in PowerShell, or the equivalent in your IR tooling), force a password reset, and check AuditLogs for any devices registered on the account.

Steps 4 and 5 may be Tier 2 territory in your shop. Don’t sit on a confirmed device code phish though. Token revocation is time-sensitive because the refresh token will keep working until you kill it.

One thing your Tier 2 analyst should know

There’s a more advanced version of this attack that’s worth flagging up the chain. After the attacker steals the initial token using the Microsoft Authentication Broker app, they can use it to register their own device into your tenant and pull a Primary Refresh Token. That gives them a foothold that satisfies “compliant device” or “managed device” Conditional Access policies, which is bad news.

If you ever see a device code sign-in to the broker app followed by a Register device event in AuditLogs from the same user within an hour, that’s the advanced Storm-2372 pattern and it should go to Tier 2 or your IR team right away. Here’s the join query if your Tier 2 wants to run it ad-hoc:

let suspicious_signins =
    union SigninLogs, AADNonInteractiveUserSignInLogs
    | where TimeGenerated > ago(24h)
    | where AuthenticationProtocol == "deviceCode"
    | where AppId == "29d9ed98-a469-4536-ade2-f981bc1d605e"
    | where ResultType == 0
    | project SignInTime = TimeGenerated, UserPrincipalName, IPAddress;
AuditLogs
| where TimeGenerated > ago(24h)
| where ActivityDisplayName in ("Register device", "Add registered owner to device")
| where Result == "success"
| extend RegUser = tostring(InitiatedBy.user.userPrincipalName)
| join kind=inner suspicious_signins on $left.RegUser == $right.UserPrincipalName
| extend MinutesBetween = datetime_diff('minute', TimeGenerated, SignInTime)
| where MinutesBetween between (0 .. 60)
| project SignInTime, DeviceRegTime = TimeGenerated, MinutesBetween,
          UserPrincipalName, IPAddress, ActivityDisplayName

How this gets shut down for good

Detection is your job in the queue. The fix is at the policy level, and it’s straightforward: block device code flow in Conditional Access for everyone who doesn’t need it.

In Entra ID, that’s a Conditional Access policy with the Authentication Flows condition set to Device code flow and the grant control set to Block. Microsoft now ships a managed Conditional Access policy for this called “Block device code flow” that you can enable from the Entra admin center under Protection > Conditional Access > Managed Policies. If your devs and IT need it, scope an allow policy narrowly for them by user, location, or device platform. That single control closes the door on this technique for the rest of the workforce.

If your tenant doesn’t have that policy yet, it’s worth raising. The detections in this post are great, but a CA block beats catching it after the fact every time.

Wrap-up

Three things to take away:

  1. AuthenticationProtocol == "deviceCode" for a regular user is suspicious until proven otherwise.
  2. The same protocol plus the Microsoft Authentication Broker app (29d9ed98-a469-4536-ade2-f981bc1d605e) on a non-IT user is a Tier 2 escalation.
  3. The fix is a Conditional Access block on device code flow. Detections matter, but the policy ends the threat.

Storm-2372 isn’t going anywhere, and the smaller actors that picked this up aren’t either. Your sign-in logs already know when it’s happening in your tenant. The job is making sure you recognize it when it lands in your queue.

References and further reading

Primary threat intelligence

Microsoft documentation

Protocol specification


Detections in this post are written for Microsoft Sentinel using Entra ID diagnostic settings forwarding SigninLogs, AADNonInteractiveUserSignInLogs, and AuditLogs. Tune the allow-list to your environment before deploying.