DEV Community

Anton Staykov
Anton Staykov

Posted on

Finding Out What Your AI Agents Actually Got: Discovering Consents and Active Agents in Microsoft Entra

The first two articles in this series argued that incremental and dynamic consent, paired with Microsoft Entra Agent ID, lets an interactive AI agent earn its access in the wild — and that the resulting accumulation quietly turns the agent into the highest-value target in your environment.

That argument is only useful if the accumulation is visible. This article is about that visibility. Specifically, two questions every identity and security architect should be able to answer on demand for any agent in the tenant:

  1. What has this agent been granted?
  2. What is this agent actually doing with what it was granted?

Neither answer requires a new product. Both live in surfaces that already exist in Microsoft Entra and Microsoft Graph — they just have not been wired into most operating models yet.

The two halves of the picture

Granted permissions and exercised permissions are different things, and they live in different places.

Granted delegated permissions — the consents Aria collects every time a user approves a new scope — are stored as oauth2PermissionGrants in Microsoft Entra and exposed through Microsoft Graph. Each grant is a tuple of (client service principal, resource service principal, principal the consent was granted on behalf of, scope string). For an interactive agent, the client is the agent's identity and the principal is the human who consented.

Granted application permissions — used mostly by autonomous agents — are stored as appRoleAssignments against the agent's identiy. They require admin consent, so they should be a smaller and more deliberate list.

Exercised permissions — what the agent actually used a token against — show up first in Microsoft Entra sign-in logs, specifically the non-interactive stream. Every time an agent requests a token for a resource (Microsoft Graph, SharePoint, a custom API), Entra records a non-interactive sign-in tagged with the agent identity, the appId, and the resourceId / resourceDisplayName of what was accessed. For agent identities provisioned through Microsoft Entra Agent ID, those events also carry an agent property describing the agentType and agentSubjectType — which is what lets you separate agent traffic from the rest of the workload identity noise without guessing.

For endpoint-level detail — the exact RequestUri, method, and response code per Microsoft Graph call — Microsoft Graph activity logs are the deeper layer, available once the diagnostic setting that streams them into a Log Analytics workspace is turned on. Sign-in logs answer which resources; Graph activity logs answer which endpoints on Microsoft Graph.

Holding the granted and exercised sides side by side is the entire game.

Discovering what Aria was granted

For a single agent like Aria, a Microsoft Graph call is enough.

GET https://graph.microsoft.com/v1.0/oauth2PermissionGrants?$filter=clientId eq '{aria-service-principal-id}'
Enter fullscreen mode Exit fullscreen mode

The response is every delegated consent in force for Aria — the consenting user (principalId), the resource being accessed (resourceId, e.g. Microsoft Graph, SharePoint, the Finance API), and the space-separated scope string. Joining resourceId against /servicePrincipals resolves the resource to a name; joining principalId against /users resolves the consenter.

For a tenant-wide view — every agent identity, every grant — drop the filter and group by clientId. The shape is usually surprising the first time anyone runs it: a long tail of agents with two or three scopes, a smaller set with a dozen, and one or two outliers worth an immediate conversation with their owner.

Application permissions warrant a parallel sweep:

GET https://graph.microsoft.com/v1.0/servicePrincipals/{id}/appRoleAssignments
Enter fullscreen mode Exit fullscreen mode

If an agent identity is meant to be interactive and you find non-trivial app role assignments here, that is a finding by itself.

Note: Microsoft Entra defines an Agent Identity as a speciliazed subtype of the service principal type. That's why the above query works directly on the /servicePrincipals collection. Of course you also use the specialized collection endpoint /servicePrincipals/microosft.graph.agentIdentity/{id}/appRoleAssignments. With respect to the information we are looking for - the result will be the same.

Identifying the most active agents

Granted scopes describe potential. Sign-in logs describe behavior, and behavior is what matters for triage.

The single most useful call for tenant-wide agent visibility is a filtered read of the non-interactive sign-in stream:

GET https://graph.microsoft.com/beta/auditLogs/signIns
    ?$filter=signInEventTypes/any(t:t eq 'nonInteractiveUser') 
        and agent/agentType eq 'agenticAppInstance' 
        and agent/agentSubjectType ne 'agentIDuser'
    &$select=id,createdDateTime,userPrincipalName,appId,appDisplayName,
        resourceId,resourceDisplayName,signInEventTypes,agent
Enter fullscreen mode Exit fullscreen mode

That one query already returns the resources every agent identity in the tenant has touched in the last 24 hours (default), who the agent acted on behalf of, and how often. No diagnostic setting, no Log Analytics workspace, no extra licensing — just Microsoft Graph and the sign-in logs that Entra is already collecting.

Once those events are routed into Log Analytics (via the Entra diagnostic setting for SignInLogs / NonInteractiveUserSignInLogs), ranking the most active agents in the tenant becomes a one-liner:

AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(7d)
| where tostring(parse_json(Agent).agentType) == 'agenticAppInstance'
| summarize Calls = count(),
            DistinctResources = dcount(ResourceDisplayName),
            LastSeen = max(TimeGenerated)
            by AppId, UserPrincipalName
| sort by Calls desc
| take 25
Enter fullscreen mode Exit fullscreen mode

This single result set answers more operational questions than most identity dashboards: which agents are loud, what resource surface area they cover, and on whose behalf. The AppId ties each row back to the agent's service principal and its oauth2PermissionGrants.

A second query reveals which resources Aria is touching:

AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(30d)
| where AppId == '{aria-app-id}'
| summarize Calls = count(),
            LastCall = max(TimeGenerated)
            by ResourceDisplayName, ResourceIdentity, ConditionalAccessStatus
| sort by Calls desc
Enter fullscreen mode Exit fullscreen mode

The output is Aria's behavioral fingerprint at the resource level: Microsoft Graph, SharePoint, the Finance API, with cadence and conditional-access outcome. Sudden new entries are leading indicators that the next consent prompt is about to fire — or that something has already gone sideways.

For teams that need endpoint-level detail on Microsoft Graph specifically — exact paths, methods, response codes — MicrosoftGraphActivityLogs is the natural follow-on, joined back on ServicePrincipalId. It is a deeper layer, not the entry point.

The query that should be on every architect's wall

The most useful query joins the two halves — granted scopes against resources actually touched:

let granted = externaldata(AppId: string, ResourceIdentity: string, Scope: string)
    [/* loaded from your oauth2PermissionGrants snapshot,
         joined to servicePrincipals to resolve resourceId */];
let used = AADNonInteractiveUserSignInLogs
    | where TimeGenerated > ago(30d)
    | where tostring(parse_json(Agent).agentType) == 'agenticAppInstance'
    | distinct AppId, ResourceIdentity;
granted
| join kind=leftouter used on AppId, ResourceIdentity
| where isnull(used_AppId)
| project AppId, GrantedButUnusedResource = ResourceIdentity, Scope
Enter fullscreen mode Exit fullscreen mode

That is the grant-versus-use gap introduced in the previous article, made operational at the resource level. Every row is a resource an agent has consent for but has not touched in 30 days — a candidate for automated revocation, or at minimum a row in next quarter's access review, keyed to actual behavior rather than the calendar. Teams that have Microsoft Graph activity logs flowing can refine the same join down to the individual scope by replacing the used block with a MicrosoftGraphActivityLogs projection over Scopes.

Building this into an operating rhythm

Three lightweight habits convert these queries from one-off discoveries into a control:

  • A weekly snapshot of oauth2PermissionGrants per agent identity, written to a Log Analytics custom table or a storage account. This produces a time series of consent accumulation — the basis for velocity-based detections.
  • A scheduled top-callers query over AADNonInteractiveUserSignInLogs filtered to agentType == 'agenticAppInstance', run daily, feeding a workbook tile and an alert for sudden movers.
  • A monthly grant-versus-use report scoped to agent identities and owned by the agent's sponsor (the business-accountable role defined in Microsoft Entra Agent ID). The sponsor either justifies the unused resources and scopes or signs off on revocation.

None of this requires custom code beyond a handful of Microsoft Graph calls and Kusto queries. All of it scales with the agent population, and the entry-point query — non-interactive sign-ins filtered to agentic app instances — works against any tenant with Microsoft Entra Agent ID and the standard sign-in log retention, no extra diagnostic plumbing required.

What's next

The queries above are the minimum viable instrumentation. They work, but they are scattered across the Microsoft Graph console, the Log Analytics blade, and whatever notebook the architect happens to keep open.

The fourth and final article in this series brings them together as a custom Azure Monitor workbook for AI agent governance — agent inventory, consent accumulation, top callers, grant-versus-use gap, sponsor sign-off — designed to drop into an existing Microsoft Entra monitoring solution.

The visibility problem is solved as soon as someone decides to look. The next article is about making it impossible not to look.

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.