Access Azure AD sign-in events for service principals via the Graph API

A while back, Microsoft released one of the most important feature updates, finally allowing us to monitor service principal sign-ins. At that time, the sign-in logs were only available as part of the Azure AD blade, but there were hints that Microsoft will make them available via other endpoints too. Now, we get to explore them via the Graph API!

If you are already using the /auditLogs endpoint, chances are you will need minimal code changes in order to start collecting service principal sign-ins. In a nutshell, all you need to do is add a filter on the signInEventTypes property. The available values for said property are: interactiveUser, nonInteractiveUser, servicePrincipal, managedIdentity, and unknownFutureValue. For this scenario, we are interested in the “servicePrincipal” value. Do note that the use of the $filter operator seems to require the /beta endpoint currently, and only works with an “eq” statement. Without further ado, here’s how a query that filters only service principal sign-in events looks like:

GET$top=1&$filter=createdDateTime ge 2022-01-10T06:00:00Z and createdDateTime le 2022-01-17T06:00:00Z and signInEventTypes/any(t:t eq 'servicePrincipal')

The request itself requires both Directory.Read.All and AuditLog.Read.All permissions, and if you are using the Delegate permissions model, the user needs to also be assigned a role with sufficient permissions to access the Azure AD sign-in logs. If those requirements are met, you can query the same endpoint via the Graph explorer tool:

AADSignIns1The screenshot above is trimmed, as you can expect the full set of details/properties corresponding to the sign-in event to be returned, and there are a lot of these. Which is a good thing, don’t get me wrong – it allows you to fetch all the relevant data in a single query. We can then compare the Graph API entry against the Azure AD blade entry, which is easily done by filtering on the correlationId value:

aadsignIns2Don’t forget that entries in the Azure AD blade UI are aggregated together, so you might need to adjust the aggregation window (shouldn’t matter when filtering for specific correlationId though).

One thing to keep in mind is that currently, service principal sign-in events are not returned by default. You have to specifically request them, by adding the “signInEventTypes/any(t:t eq ‘servicePrincipal’)” filter statement. In addition, the endpoint does not currently support the $select operator, meaning you cannot limit the output to just select properties. You can of course do so client-side.

For the sake of completeness, here’s also sample PowerShell-based code to query service principal events. Make sure to obtain a valid access token with sufficient permissions first, and pass it as part of the $authHeader1 variable.

$GraphSP = @()
$uri = "`$top=100&`$filter=createdDateTime ge 2022-01-15T06:00:00Z and createdDateTime le 2022-01-17T06:00:00Z and signInEventTypes/any(t:t eq 'servicePrincipal')"

do {
$result = Invoke-WebRequest -Uri $uri -Verbose:$VerbosePreference -ErrorAction Stop -Headers $authHeader1
$uri = ($result.Content | ConvertFrom-Json).'@odata.nextLink'
#If we are getting multiple pages, best add some delay to avoid throttling
Start-Sleep -Milliseconds 500
$GraphSP += ($result.Content | ConvertFrom-Json).Value
} while ($uri)

Once you obtain the sign-in entries, you can manipulate them as any other PowerShell object:

PS C:\> $GraphSP[0].appId

PS C:\> $GraphSP[0].status

errorCode failureReason additionalDetails
--------- ------------- -----------------
0 Other.

And in case you were wondering whether the Management activities API, the Audit UI in the Compliance center or the Search-UnifiedAuditLog cmdlet expose such events, the answer seems to be NO. The Graph API method outlined above or the Azure AD blade are your options, as well as exporting to a event hub, etc.

5 thoughts on “Access Azure AD sign-in events for service principals via the Graph API

  1. Tobias says:

    This was working great until today?

    This no longer works and just returns all sign ins? Are you experiencing the same?

  2. Darryl says:

    Interesting, I have been doing the same exercise and found that the Invoke-RestMethod essentially ignores the filter and only returns user signins. Did you find the same happen to you during your testing?

    1. Vasil Michev says:

      Works fine here, make sure you’re escaping the ‘$’ character though. Or any other special chars for that matter.


Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.