Now You See Me: AADGraphActivityLogs

jmrgz1 pts0 comments

Now You See Me: AADGraphActivityLogs - Cloudbrothers

Contents

Now You See Me: AADGraphActivityLogs

Fabian Bader included in Azure AD Entra Entra ID Security KQL

2026-05-10 2049 words<br>10 minutes

Contents

In my series &ldquo;Detect threats using *GraphActivityLogs&rdquo; I covered a lot of the basics on how to use different methods to detect certain reconnaissance tooling based on fingerprinting the specific sequence of requests or the volume of requests made to the Microsoft Graph endpoints. But one of the biggest detection gaps in all this was the Azure AD Graph, the old API on a retirement path that started before some of you might work in cyber security.<br>All this changed in early May 2026 with the introduction of the AADGraphActivityLogs as a new log source. And don&rsquo;t get me wrong, this table was in the official documentation since May 2025 but no data was flowing. After a long private preview Microsoft, almost silently released the log to all their customers and gave them crucial insights in one of the most abused protocols for reconnaissance. Notably toolkits like ROADtools and AADInternals use this API to gather deep insights into the tenant and attack vectors like the Intune Company portal Conditional Access bypass rely on the default grant to this resource.

Enable logs

Like all Entra ID logs you can configure the log forwarding of the AADGraphActivityLogs in the Diagnostic Settings of Entra ID. For my purpose I forward them to a Sentinel enabled Log Analytics workspace.

Entra ID Diagnostic Settings page showing AADGraphActivityLogs forwarding configuration to Log Analytics workspace<br>">

Entra ID Diagnostic Settings page showing AADGraphActivityLogs forwarding configuration to Log Analytics workspace

Log schema

While the log schema itself is very extensive, let&rsquo;s concentrate on the fields that are crucial for detection engineers and defenders.<br>AADGraphActivityLogs schema showing key fields including TimeRequested, RequestMethod, ResponseStatusCode, and UserAgent<br>">

AADGraphActivityLogs schema showing key fields including TimeRequested, RequestMethod, ResponseStatusCode, and UserAgent

Note

TimeGenerated and TimeRequested are important to understand and the median time difference between the two, in my lab environment, is about 7 minutes, with a maximum difference of 70 minutes. Make sure to adjust your queries and detections for those values.

TimeRequested<br>The actual time the AAD Graph API call was made

RequestMethod<br>The REST API method used (e.g. POST, GET, PATCH, DELETE)

ResponseStatusCode<br>Response from the API itself (200 - 204, 302, 303, 400, 403, 404)

ResponseSizeBytes<br>The total size of the Graph response

SignInActivityId<br>The related signin activity Id that can be used to map this to the signin logs

TokenIssuedAt<br>Date and time when the access token was issued by Entra ID

ActorType<br>Either User or Application

ServicePrincipalId or UserId<br>The object Id of the identity

AppId<br>The app id that was used to connect to Azure AD graph

UserAgent<br>The UserAgent the client provided

RequestUri<br>The actual API endpoint requested

CallerIpAddress<br>The IP address from where the API call was made

Detection opportunities

UserAgent

Especially the UserAgent can be used for simple detections, since attackers did not have to give too much thought about opsec for this API, they might forget to change the source code and looking for AADInternals or aiohttp might expose adversaries already.

AADGraphActivityLogs<br>| where UserAgent has "aiohttp"<br>| extend ObjectId = iff(isempty(UserId), ServicePrincipalId, UserId)<br>| extend ObjectType = iff(isempty(UserId), "ServicePrincipalId", "UserId")<br>| project-reorder TimeGenerated, TimeRequested, ObjectId, ObjectType, RequestUri, RequestMethod, ResponseStatusCode, UserAgent

RequestUri

Gathering information for a whole tenant requires a lot of requests to different endpoints, and this is your chance to find abusers. The AAD Graph API has only a few official use cases left, so the requests to this API are by far more normalised and uniform than what you see in the Microsoft Graph API. This helps immensely baselining activity.<br>As in my last blog posts about the Graph API, I created a hunting query which you can use to find ROADtools based on the endpoints the default gather parameter requests.

let ToolGraphQueries = dynamic([<br>"servicePrincipals",<br>"groups",<br>"roleAssignments",<br>"eligibleRoleAssignments",<br>"applicationRefs",<br>"directoryRoles",<br>"directoryObjects",<br>"applications",<br>"policies",<br>"oauth2PermissionGrants",<br>"administrativeUnits",<br>"roleDefinitions",<br>"authorizationPolicy",<br>"devices",<br>"contacts",<br>"settings",<br>"tenantDetails",<br>"users"<br>]);<br>AADGraphActivityLogs<br>| extend ObjectId = iff(isempty(UserId), ServicePrincipalId, UserId)<br>| extend ObjectType = iff(isempty(UserId), "ServicePrincipalId", "UserId")<br>| extend Endpoint = extract(@'^/(?:v2/)?[^/]+/([A-Za-z0-9$]+)', 1, RequestUri)<br>| summarize<br>GraphEndpointsCalled = make_set(Endpoint,...

aadgraphactivitylogs userid graph useragent entra requests

Related Articles