Enriched Entra Sign In Logs Requested Token By Suspicious RT
Query
// Requested access and refresh tokens outside of Global Secure Access
// Hunting query which helps to get an overview of issued access or refresh tokens outside of Global Secure Access
// by using a bounded refresh token without IncomingTokenType
// External lookup to get list of FOCI applications
let FociClientApplications = (externaldata(client_id: string)
[@"https://raw.githubusercontent.com/secureworks/family-of-client-ids-research/refs/heads/main/known-foci-clients.csv"] with (format="csv", ignoreFirstRecord=true)
| project-rename FociClientId = client_id
);
// External lookup for ErrorCode description
let ErrorCodes = (externaldata(ResultType: string, Message: string)
[@"https://raw.githubusercontent.com/f-bader/EntraID-ErrorCodes/refs/heads/main/EntraIDErrorCodes.csv"] with (format="csv", ignoreFirstRecord=true)
| project-rename ResultErrorDescription = Message
| project ResultType, ResultErrorDescription
);
union SigninLogs, AADNonInteractiveUserSignInLogs
// Optional: Filter for specific time window, user and only successful sign-ins
// | where UserPrincipalName == "<UserPrincipalName>"
// and CreatedDateTime between ( todatetime('<StartTime>') .. todatetime('<EndTime>') )
// and ResultType == "0"
// Filter for sign-ins to home tenant only
| where HomeTenantId == ResourceTenantId
// Enrichment of device details
| extend DeviceDetail = iff(isempty(DeviceDetail_dynamic), todynamic(DeviceDetail_string), DeviceDetail_dynamic)
| extend DeviceId = tostring(tolower(DeviceDetail.deviceId))
| extend DeviceName = tostring(toupper(DeviceDetail.displayName))
// Enrichment of token protection details
| extend TokenProtectionStatus = iff(isempty(TokenProtectionStatusDetails_dynamic), todynamic(TokenProtectionStatusDetails_string), TokenProtectionStatusDetails_dynamic)
| extend SignInSessionStatus = TokenProtectionStatus.signInSessionStatus
// Lookup for FOCI client
| join kind=inner (FociClientApplications) on $left.AppId == $right.FociClientId
| extend IsFoci = iff((AppId == FociClientId), "true", "false")
| join kind = leftouter (NetworkAccessTraffic
// Correlation with GSA network traffic
| project
TimeGenerated,
TransactionId,
ConnectionId,
IPAddress = SourceIp,
AgentVersion,
UserId,
DeviceId,
UniqueTokenIdentifier = UniqueTokenId,
InitiatingProcessName
)
on UserId, DeviceId, UniqueTokenIdentifier, IPAddress
| extend TokenAcquiredThroughGsa = iff((isnotempty(ConnectionId)), "true", "false")
// Lookup for Error Code
| join kind=leftouter (ErrorCodes) on ResultType
// Filtering for specific token properties
| where IncomingTokenType == "none" and SignInSessionStatus == "bound"
| project
StolenTokenType = IncomingTokenType,
StolenTokenApp = AppDisplayName,
StolenTokenFromGsa = TokenAcquiredThroughGsa,
StolenTokenStatus = SignInSessionStatus,
StolenTokenUniqueTokenId = UniqueTokenIdentifier,
StolenTokenProcessName = InitiatingProcessName,
StolenTokenIsFoci = IsFoci,
SessionId,
CorrelationId,
ResultType,
OriginalRequestId,
IPAddress,
StolenTimestamp = CreatedDateTime
// Lookup to get error codes
| join kind=leftouter (ErrorCodes) on ResultType
// Get details of UniqueTokenIdentifier and TokenProtection for Diagnostic logs
| join kind = inner (
union AADNonInteractiveUserSignInLogs, SigninLogs
// Enrichment of device details
| extend DeviceDetail = iff(isempty(DeviceDetail_dynamic), todynamic(DeviceDetail_string), DeviceDetail_dynamic)
| extend DeviceId = tostring(tolower(DeviceDetail.deviceId))
| extend DeviceName = tostring(toupper(DeviceDetail.displayName))
| extend TokenProtectionStatus = iff(isempty(TokenProtectionStatusDetails_dynamic), todynamic(TokenProtectionStatusDetails_string), TokenProtectionStatusDetails_dynamic)
| extend SignInSessionStatus = TokenProtectionStatus.signInSessionStatus
| join kind=leftouter (FociClientApplications) on $left.AppId == $right.FociClientId
| extend IsFoci = iff((AppId == FociClientId), "true", "false")
| project
SessionId,
CorrelationId,
ErrorCode = ResultType,
ResultType,
OriginalRequestId,
IPAddress,
UniqueTokenIdentifier,
SignInSessionStatus,
IsFoci,
DeviceId,
CreatedDateTime,
AppDisplayName,
ResourceDisplayName,
UserId
) on SessionId, CorrelationId, ResultType, OriginalRequestId, IPAddress
// Remove tokens which has been issued from compliant network
| join kind = anti (NetworkAccessTraffic
| where TimeGenerated >ago(1d)
| project
TimeGenerated,
TransactionId,
ConnectionId,
IPAddress = SourceIp,
AgentVersion,
UserId,
DeviceId,
UniqueTokenIdentifier = UniqueTokenId,
InitiatingProcessName
) on UserId, DeviceId, UniqueTokenIdentifier, IPAddress
| extend IsOutsideOfGsa = "true"
| project UserPrincipalName, StolenTimestamp, StolenTokenType, StolenTokenApp, StolenTokenIsFoci, StolenTokenStatus, StolenTokenProcessName, SessionId, SignInTime = CreatedDateTime, AppDisplayName, ResourceDisplayName, SignInSessionStatus, IsFoci, IsOutsideOfGsa, UniqueTokenIdentifier, ErrorCode, ResultErrorDescription
| sort by SignInTime ascExplanation
This query is designed to identify and analyze access or refresh tokens that were issued outside of a secure network environment, specifically outside of Global Secure Access (GSA). Here's a simplified breakdown of what the query does:
-
External Data Sources:
- It pulls in a list of known Family of Client IDs (FOCI) applications and error code descriptions from external CSV files hosted on GitHub.
-
Data Union:
- It combines data from two sources:
SigninLogsandAADNonInteractiveUserSignInLogs, which contain sign-in information.
- It combines data from two sources:
-
Filtering:
- The query filters for sign-ins that occurred within the user's home tenant.
- It focuses on tokens that do not have an
IncomingTokenTypeand have aSignInSessionStatusof "bound".
-
Data Enrichment:
- It enriches the data with device details and token protection details.
- It checks if the application is a FOCI client by joining with the FOCI applications list.
- It correlates the sign-in data with network traffic data to determine if the token was acquired through GSA.
-
Error Code Lookup:
- It joins with the error codes data to provide descriptions for any error codes found.
-
Token Identification:
- It identifies tokens that were issued outside of the GSA by excluding those that have been issued from a compliant network.
-
Output:
- The query projects a set of fields that include user details, token details, application details, and error descriptions.
- It sorts the results by the sign-in time in ascending order.
Overall, this query helps security analysts identify potentially unauthorized or risky token issuances by highlighting tokens that were issued outside of a secure network environment.
Details

Thomas Naunheim
Released: August 19, 2025
Tables
SigninLogsAADNonInteractiveUserSignInLogsNetworkAccessTraffic
Keywords
TokensDevicesApplicationsNetworkUsersLogs
Operators
letexternaldataproject-renameprojectunionwhereextendiffisemptytodynamictostringtolowertoupperjoinonkindsortasc