Behavior-based detection engineering
High-Risk User with New Device Code Authentication
Detects a medium/high active user-risk event aligned with the user's first successful device-code authentication in the recent baseline, then exposes phishing and post-compromise pivots.
Behavior
What it detects
An identity with an active medium/high risk event completes a successful device-code authentication that has no recent user baseline and occurs near the risk event.
Engineering decision
Why this detection
A generic rule for every risky user would be noisy, and a generic rule for every device-code sign-in would punish legitimate CLI, IoT, and shared-device workflows.
This analytic combines the two: an active medium/high identity-risk event must align with the user's first successful device-code authentication in the recent baseline.
The primary query is intentionally infrastructure-agnostic. Campaign IPs and hosting providers can rotate; identity risk, authentication protocol, and per-user baseline are more durable starting points. The drilldowns then test the Microsoft-published 50199-to-success pattern, URL-click correlation, and post-compromise cloud behavior.
Signal chain
Detection logic
- Collect active medium/high user-risk events from Entra ID Protection.
- Collect successful device-code sign-ins and normalize the user identity.
- Build a thirty-day per-user baseline for legitimate successful device-code use.
- Require the current device-code sign-in to occur near the active risk event.
- Generate a candidate only when the user has no recent device-code history.
- Use risk event type, IP, app, resource, session, and correlation identifiers for investigation.
- Confirm legitimate CLI, IoT, shared-device, or administrative workflows before containment.
Primary analytic
Query
Q-01Detection logicNew device-code sign-in for an actively risky user
What this checks
Generate a high-priority candidate when a medium/high active user-risk event aligns with the user's first successful device-code authentication in the thirty-day baseline.
KQL
let current_window = 1d;
let baseline_window = 30d;
let active_risk =
AADUserRiskEvents
| where TimeGenerated >= ago(current_window)
| where RiskLevel in~ ("medium", "high")
| where RiskState in~ ("atRisk", "confirmedCompromised")
| extend AccountUpn=tolower(UserPrincipalName)
| summarize
RiskTime=max(TimeGenerated),
RiskEvents=make_set(RiskEventType, 20),
RiskLevels=make_set(RiskLevel, 10),
RiskStates=make_set(RiskState, 10),
RiskIPs=make_set(IpAddress, 20),
RiskCorrelations=make_set(CorrelationId, 20)
by AccountUpn;
let historical_device_code =
SigninLogs
| where TimeGenerated between (ago(baseline_window) .. ago(current_window))
| where AuthenticationProtocol =~ "deviceCode"
| where tostring(ResultType) == "0"
| extend AccountUpn=tolower(UserPrincipalName)
| summarize PriorDeviceCode=count() by AccountUpn;
SigninLogs
| where TimeGenerated >= ago(current_window)
| where AuthenticationProtocol =~ "deviceCode"
| where tostring(ResultType) == "0"
| extend AccountUpn=tolower(UserPrincipalName)
| join kind=inner active_risk on AccountUpn
| where TimeGenerated between (RiskTime - 30m .. RiskTime + 30m)
| join kind=leftouter historical_device_code on AccountUpn
| extend PriorDeviceCode=coalesce(PriorDeviceCode,0)
| where PriorDeviceCode == 0
| project
TimeGenerated,
RiskTime,
AccountUpn,
IPAddress,
AuthenticationProtocol,
AuthenticationRequirement,
RiskLevelDuringSignIn,
RiskLevelAggregated,
RiskState,
AppDisplayName,
AppId,
ResourceDisplayName,
ConditionalAccessStatus,
DeviceDetail,
CorrelationId,
SessionId,
RiskEvents,
RiskLevels,
RiskStates,
RiskIPs,
PriorDeviceCode
| order by TimeGenerated descSPL
(
index=<entra_risk_index> sourcetype=<entra_user_risk_sourcetype> earliest=-1d
)
OR
(
index=<entra_signin_index> sourcetype=<entra_signin_sourcetype> earliest=-30d
)
| eval
user=lower(coalesce(user, UserPrincipalName, user_principal_name)),
event_type=case(
isnotnull(RiskEventType),"risk",
lower(coalesce(AuthenticationProtocol,auth_protocol))="devicecode","device_code_signin",
true(),"other"
),
risk_level=lower(coalesce(risk_level,RiskLevel)),
risk_state=lower(coalesce(risk_state,RiskState)),
result=coalesce(result,ResultType),
src=coalesce(src,IPAddress,IpAddress),
app_name=coalesce(app_name,AppDisplayName),
session_id=coalesce(session_id,SessionId),
correlation_id=coalesce(correlation_id,CorrelationId),
is_current=if(_time>=relative_time(now(),"-1d"),1,0)
| eventstats
count(eval(event_type="device_code_signin" AND tostring(result)="0" AND is_current=0)) as prior_device_code
by user
| sort 0 user _time
| streamstats current=f
last(eval(if(event_type="risk" AND risk_level IN ("medium","high") AND risk_state IN ("atrisk","confirmedcompromised"),_time,null()))) as risk_time
last(eval(if(event_type="risk",risk_level,null()))) as last_risk_level
last(eval(if(event_type="risk",risk_state,null()))) as last_risk_state
by user
| where
event_type="device_code_signin"
AND tostring(result)="0"
AND is_current=1
AND prior_device_code=0
AND isnotnull(risk_time)
AND abs(_time-risk_time)<=1800
| table _time risk_time user src app_name session_id correlation_id last_risk_level last_risk_state prior_device_code
| sort - _timeWhat to look for
A medium/high risky identity completes a successful device-code authentication that is new for the user and occurs near the risk event.
Technical details
Tested signal
Active medium/high identity risk plus first-seen successful device-code authentication for the same user in a bounded time window.
Assumptions
- AADUserRiskEvents and SigninLogs are available with consistent UPN values.
- Thirty days is a useful local baseline for expected device-code use.
- Full user/sign-in risk detail requires Microsoft Entra ID P2.
Data requirements and relevant fields
- identity
Microsoft Entra ID Protection user-risk events with identity, event type, risk level/state, source IP, and sign-in correlation identifiers.
TimeGeneratedActivityDateTimeDetectedDateTimeUserPrincipalNameUserIdIpAddressCorrelationIdRequestIdRiskEventTypeRiskLevelRiskStateRiskDetailDetectionTimingTypeActivity
- authentication
Microsoft Entra sign-in logs with authentication protocol, risk state, result, application, device, IP, and correlation/session context.
TimeGeneratedUserPrincipalNameUserIdIPAddressAuthenticationProtocolAuthenticationRequirementResultTypeResultDescriptionRiskLevelDuringSignInRiskLevelAggregatedRiskStateCorrelationIdSessionIdAppDisplayNameAppIdResourceDisplayNameConditionalAccessStatusDeviceDetailAutonomousSystemNumber
KQL schema
Validate table availability, Entra ID Protection licensing, connector retention, and local field population.
SPL schema
Replace index/sourcetype placeholders and map user-risk and sign-in concepts to the local Entra export.
Limitations
- Approved CLI/IoT/shared-device workflows can legitimately use device-code flow.
- Offline risk detections can shift the apparent timing of the risk event.
- Organizations with normal device-code usage should add app/resource/user-population baselines instead of globally suppressing the flow.
This analytic intentionally starts from identity risk and then requires new successful device-code use. It is not a generic 'all risky users' alert and not a blanket 'all device code is malicious' rule.
Analyst workflow
What the analyst should look for
- Why is the user currently medium/high risk?
- Is device-code flow expected for this user or application?
- Has the user successfully used device-code flow in the previous thirty days?
- Does the sign-in show medium/high risk or an at-risk state?
- Is there a 50199-to-success sequence for the same session or correlation context?
- Was there a URL click shortly before the risky sign-in?
- Did the account register a new device after authentication?
- Were inbox rules or unusual mail access observed after the suspicious session?
- Does an approved CLI, IoT device, shared device, or administrative workflow explain the event?
Expected result
A medium/high risky identity completes a successful device-code authentication that is new for the user and occurs near the risk event.
Investigation pivots
Drilldowns
View query — Rebuild the user's risk timeline
Q-02DrilldownRebuild the user's risk timeline
What this checks
List every risk detection for the candidate identity around the device-code sign-in.
KQL
let target_user = "<candidate_user>";
AADUserRiskEvents
| where TimeGenerated >= ago(7d)
| where UserPrincipalName =~ target_user
| where RiskLevel in~ ("medium", "high")
| where RiskState in~ ("atRisk", "confirmedCompromised")
| project
TimeGenerated,
ActivityDateTime,
DetectedDateTime,
UserPrincipalName,
UserId,
IpAddress,
RiskEventType,
RiskLevel,
RiskState,
RiskDetail,
DetectionTimingType,
Activity,
CorrelationId,
RequestId
| order by TimeGenerated ascSPL
index=<entra_risk_index> sourcetype=<entra_user_risk_sourcetype>
earliest=-7d
| eval
user=lower(coalesce(user, UserPrincipalName, user_principal_name)),
src=coalesce(src, IpAddress, ip_address),
risk_type=coalesce(risk_type, RiskEventType),
risk_level=lower(coalesce(risk_level, RiskLevel)),
risk_state=lower(coalesce(risk_state, RiskState)),
correlation_id=coalesce(correlation_id, CorrelationId),
request_id=coalesce(request_id, RequestId)
| where user="<candidate_user>"
AND risk_level IN ("medium","high")
AND risk_state IN ("atrisk","confirmedcompromised")
| table _time user src risk_type risk_level risk_state correlation_id request_id
| sort 0 _timeWhat to look for
Risk event types, states, IPs, and timing that explain why the identity was elevated.
Technical details
Tested signal
User-risk events surrounding the candidate authentication.
Assumptions
- Replace the target user with the candidate from Q-01.
Data requirements and relevant fields
- identity
Microsoft Entra ID Protection user-risk events with identity, event type, risk level/state, source IP, and sign-in correlation identifiers.
TimeGeneratedActivityDateTimeDetectedDateTimeUserPrincipalNameUserIdIpAddressCorrelationIdRequestIdRiskEventTypeRiskLevelRiskStateRiskDetailDetectionTimingTypeActivity
KQL schema
Validate table availability, Entra ID Protection licensing, connector retention, and local field population.
SPL schema
Replace index/sourcetype placeholders and map user-risk and sign-in concepts to the local Entra export.
Limitations
- Offline risk events can be detected after the source activity.
KQL uses Microsoft Entra / Defender XDR identity telemetry. SPL is a normalized raw-event scaffold and requires local field mapping.
View query — Validate the 50199-to-success sequence
Q-03DrilldownValidate the 50199-to-success sequence
What this checks
Check whether the candidate identity shows Microsoft's documented user-interrupt-to-success authentication pattern.
KQL
let target_user = "<candidate_user>";
EntraIdSigninEvents
| where Timestamp >= ago(7d)
| where AccountUpn =~ target_user
| where ErrorCode in (0, 50199)
| summarize
FirstSeen=min(Timestamp),
LastSeen=max(Timestamp),
ErrorCodes=make_set(ErrorCode),
IPAddresses=make_set(IPAddress, 20),
RiskLevels=make_set(RiskLevelDuringSignin, 10),
Applications=make_set(Application, 20)
by AccountUpn, CorrelationId, SessionId
| where ErrorCodes has_all (0, 50199)
| extend Duration=LastSeen-FirstSeen
| where Duration <= 5m
| order by FirstSeen ascSPL
index=<entra_signin_index> sourcetype=<defender_entra_signin_sourcetype>
earliest=-7d
| eval
user=lower(coalesce(user, AccountUpn)),
error_code=coalesce(error_code, ErrorCode),
correlation_id=coalesce(correlation_id, CorrelationId),
session_id=coalesce(session_id, SessionId),
src=coalesce(src, IPAddress)
| where user="<candidate_user>" AND error_code IN (0,50199)
| stats
min(_time) as first_seen
max(_time) as last_seen
values(error_code) as error_codes
values(src) as source_ips
values(RiskLevelDuringSignin) as risk_levels
by user correlation_id session_id
| eval duration=last_seen-first_seen
| where mvfind(error_codes,"50199")>=0 AND mvfind(error_codes,"0")>=0 AND duration<=300
| convert ctime(first_seen) ctime(last_seen)
| sort 0 first_seenWhat to look for
A short 50199-to-success sequence that aligns with the risky device-code session.
Technical details
Tested signal
50199 and success for the same user/session/correlation context within five minutes.
Assumptions
- Defender XDR Entra sign-in telemetry is available for the candidate identity.
Data requirements and relevant fields
- authentication
Microsoft Defender XDR Entra sign-in events used for the documented 50199-to-success device-code investigation pattern.
TimestampAccountUpnAccountObjectIdIPAddressErrorCodeRiskLevelDuringSigninCorrelationIdSessionIdApplicationResourceDisplayNameCallReportId
KQL schema
Validate table availability, Entra ID Protection licensing, connector retention, and local field population.
SPL schema
Replace index/sourcetype placeholders and map user-risk and sign-in concepts to the local Entra export.
Limitations
- 50199 is not malicious by itself.
KQL uses Microsoft Entra / Defender XDR identity telemetry. SPL is a normalized raw-event scaffold and requires local field mapping.
View query — Correlate URL clicks with risky sign-ins
Q-04DrilldownCorrelate URL clicks with risky sign-ins
What this checks
Find a Defender for Office 365 URL click followed by a successful risky sign-in for the same user in the short campaign-relevant window published by Microsoft.
KQL
let candidate_user = "<candidate_user>";
let clicks =
UrlClickEvents
| where Timestamp >= ago(7d)
| extend AccountUpn=tolower(AccountUpn)
| where AccountUpn == tolower(candidate_user)
| project ClickTime=Timestamp, ActionType, UrlChain, NetworkMessageId, Url, AccountUpn;
EntraIdSigninEvents
| where Timestamp >= ago(7d)
| where ErrorCode == 0
| where RiskLevelDuringSignin in (10, 50, 100)
| extend AccountUpn=tolower(AccountUpn)
| where AccountUpn == tolower(candidate_user)
| join kind=inner clicks on AccountUpn
| where (Timestamp - ClickTime) between (-2m .. 7m)
| project Timestamp, ClickTime, AccountUpn, RiskLevelDuringSignin, SessionId, CorrelationId, IPAddress, Url, UrlChain, NetworkMessageId, ActionType
| order by Timestamp ascSPL
(
index=<url_click_index> sourcetype=<defender_url_click_sourcetype> earliest=-7d
)
OR
(
index=<entra_signin_index> sourcetype=<defender_entra_signin_sourcetype> earliest=-7d
)
| eval
user=lower(coalesce(user,AccountUpn)),
event_type=case(
isnotnull(NetworkMessageId),"url_click",
isnotnull(ErrorCode),"signin",
true(),"other"
),
error_code=coalesce(error_code,ErrorCode),
signin_risk=coalesce(signin_risk,RiskLevelDuringSignin),
session_id=coalesce(session_id,SessionId),
correlation_id=coalesce(correlation_id,CorrelationId),
src=coalesce(src,IPAddress)
| where user="<candidate_user>"
| sort 0 user _time
| streamstats current=f
last(eval(if(event_type="url_click",_time,null()))) as click_time
last(eval(if(event_type="url_click",Url,null()))) as clicked_url
last(eval(if(event_type="url_click",NetworkMessageId,null()))) as message_id
by user
| where event_type="signin" AND error_code=0 AND signin_risk IN (10,50,100)
AND isnotnull(click_time) AND (_time-click_time)>=-120 AND (_time-click_time)<=420
| table _time click_time user signin_risk src session_id correlation_id clicked_url message_id
| sort 0 _timeWhat to look for
A click and risky successful sign-in for the same identity within a few minutes, strengthening the phishing hypothesis.
Technical details
Tested signal
URL click followed within minutes by a successful risky Entra sign-in.
Assumptions
- UrlClickEvents and EntraIdSigninEvents are available in Defender XDR.
- The user's email identity is normalized consistently across both tables.
Data requirements and relevant fields
Defender for Office 365 URL click telemetry with user, URL chain, message ID, action, and click timestamp.
TimestampAccountUpnActionTypeUrlUrlChainNetworkMessageIdWorkload
- authentication
Microsoft Defender XDR Entra sign-in events used for the documented 50199-to-success device-code investigation pattern.
TimestampAccountUpnAccountObjectIdIPAddressErrorCodeRiskLevelDuringSigninCorrelationIdSessionIdApplicationResourceDisplayNameCallReportId
KQL schema
Validate table availability, Entra ID Protection licensing, connector retention, and local field population.
SPL schema
Replace index/sourcetype placeholders and map user-risk and sign-in concepts to the local Entra export.
Limitations
- A click and risky sign-in can coincide for benign reasons; preserve the message, URL chain, authentication protocol, and session context.
KQL uses Microsoft Entra / Defender XDR identity telemetry. SPL is a normalized raw-event scaffold and requires local field mapping.
View query — Check device registration and mailbox activity
Q-05DrilldownCheck device registration and mailbox activity
What this checks
Scope Microsoft-observed post-compromise actions after the candidate device-code session.
KQL
let target_user = "<candidate_user>";
CloudAppEvents
| where Timestamp >= ago(7d)
| where AccountObjectId != ""
| extend UserPrincipalName = tolower(tostring(RawEventData.ObjectId))
| where UserPrincipalName == tolower(target_user)
or AccountDisplayName =~ target_user
| where AccountDisplayName == "Device Registration Service"
or ActionType in (
"New-InboxRule",
"Set-InboxRule",
"Enable-InboxRule",
"UpdateInboxRules",
"MailItemsAccessed"
)
| project
Timestamp,
AccountDisplayName,
ActionType,
ApplicationId,
IPAddress,
UncommonForUser,
ActivityObjects,
RawEventData
| order by Timestamp ascSPL
index=<cloud_app_index> sourcetype=<cloud_app_events_sourcetype>
earliest=-7d
| eval
user=lower(coalesce(user, UserPrincipalName, AccountDisplayName)),
action=coalesce(action, ActionType),
app_id=coalesce(app_id, ApplicationId),
src=coalesce(src, IPAddress)
| where user="<candidate_user>"
AND (
action IN ("New-InboxRule","Set-InboxRule","Enable-InboxRule","UpdateInboxRules","MailItemsAccessed")
OR AccountDisplayName="Device Registration Service"
)
| fields _time user src action app_id AccountDisplayName UncommonForUser ActivityObjects RawEventData
| sort 0 _timeWhat to look for
A new device registration, inbox-rule change, or unusual mail access event following the suspicious session.
Technical details
Tested signal
Device registration or Exchange mailbox actions by the risky identity after authentication.
Assumptions
- CloudAppEvents is available for the candidate user's post-authentication window.
Data requirements and relevant fields
- saas audit
Defender XDR cloud application events for device registration, Exchange actions, and uncommon user activity after compromise.
TimestampAccountObjectIdAccountDisplayNameApplicationIdActionTypeIPAddressActivityObjectsRawEventDataUncommonForUser
KQL schema
Validate table availability, Entra ID Protection licensing, connector retention, and local field population.
SPL schema
Replace index/sourcetype placeholders and map user-risk and sign-in concepts to the local Entra export.
Limitations
- Different campaign victims followed different post-compromise branches.
KQL uses Microsoft Entra / Defender XDR identity telemetry. SPL is a normalized raw-event scaffold and requires local field mapping.
Legitimate resemblance
What the analyst should confirm
An administrator intentionally uses a CLI that authenticates through device-code flow while traveling or using a new network.
The application, resource, admin role, ticket/change window, source context, and repeated prior device-code history all match approved administration.A shared device, digital signage system, or input-constrained device legitimately uses device-code flow for onboarding or renewal.
The device/app/resource combination is registered in inventory, the owner is known, and the user population and timing match the documented workflow.Identity Protection raises risk due to unfamiliar properties while a legitimate first-time device-code workflow occurs.
The risk is explained by expected travel/network/device change and there is no suspicious URL click, 50199 sequence, device registration, mailbox activity, or other post-compromise evidence.
Confirmed match
Action after a confirmed match
- Disable the user temporarily when active compromise is likely.
- Revoke refresh tokens and sign-in sessions.
- Remove malicious or untrusted device registrations.
- Review and remove malicious inbox rules or other cloud persistence.
- Scope Microsoft 365 and Graph activity after token acquisition.
- Block device-code flow through Conditional Access when it is not required.
- Require phishing-resistant authentication for high-value identities and use risk-based Conditional Access.
Threat hunt
Could this be happening elsewhere?
Hunt for this behavior across the environment.Technical boundary
Telemetry and limitations
- Identity
Microsoft Entra ID Protection user-risk events with identity, event type, risk level/state, source IP, and sign-in correlation identifiers.
Required fieldsTimeGeneratedActivityDateTimeDetectedDateTimeUserPrincipalNameUserIdIpAddressCorrelationIdRequestIdRiskEventTypeRiskLevelRiskStateRiskDetailDetectionTimingTypeActivity
- Authentication
Microsoft Entra and Defender XDR sign-in telemetry with authentication protocol, result/error, user risk, application, device, IP, session, and correlation context.
Required fieldsTimeGeneratedUserPrincipalNameUserIdIPAddressAuthenticationProtocolAuthenticationRequirementResultTypeResultDescriptionRiskLevelDuringSignInRiskLevelAggregatedRiskStateCorrelationIdSessionIdAppDisplayNameAppIdResourceDisplayNameConditionalAccessStatusDeviceDetailAutonomousSystemNumberTimestampAccountUpnAccountObjectIdErrorCodeRiskLevelDuringSigninApplicationCallReportId
Defender for Office 365 URL click telemetry with user, URL chain, message ID, action, and click timestamp.
Required fieldsTimestampAccountUpnActionTypeUrlUrlChainNetworkMessageIdWorkload
- Saas Audit
Defender XDR cloud application events for device registration, Exchange actions, and uncommon user activity after compromise.
Required fieldsTimestampAccountObjectIdAccountDisplayNameApplicationIdActionTypeIPAddressActivityObjectsRawEventDataUncommonForUser
Blind spots
- Tenants without Entra ID P2 can have hidden or reduced identity-risk detail.
- A user with legitimate historical device-code activity can still be phished through the same flow, so the baseline lowers noise but does not prove safety.
- Offline risk detections can arrive after the associated authentication and complicate strict time correlation.
- If Defender for Office 365 click telemetry is unavailable, the phishing-lure correlation is weaker.
- Post-compromise token use can appear as valid cloud activity and may require workload audit telemetry beyond sign-in logs.
Behavior mapping
MITRE ATT&CK
T1528· Steal Application Access TokenThe risky device-code authentication can produce access and refresh tokens for an attacker-controlled session.
T1078.004· Cloud AccountsThe compromised cloud account is subsequently accessed with valid token-backed identity context.
T1566.002· Spearphishing LinkCurrent campaigns deliver the device-code workflow through phishing links.
T1204.001· Malicious LinkUser interaction completes the attacker's pending device-code authorization.
Mappings describe the behavior examined by this analytic. They do not prove attribution, deployment, or technique-wide coverage.
Review boundary
Sources and limits
- External sources
- 6
Exact fields, retention, and operational thresholds remain environment-specific.