3.5.3. Microsoft Graph Activity Logs
💡 First Principle: Modern attacks increasingly use APIs rather than user interfaces. A malicious OAuth app, a stolen token replayed from a script, or an attacker enumerating the directory all talk to Microsoft Graph. Graph activity logs record every HTTP request Graph processes for your tenant, so they reveal what an identity or app did with its access, not just that it signed in.
Each record captures the request (URI, HTTP method, response status code, size, duration), the caller (app ID, service principal ID, user ID, client authentication method), the token and session context (scopes, roles, unique token ID, sign-in activity ID, session ID), and the network context (IP address, user agent, location). That combination answers questions sign-in logs can't: which mailboxes an app read, which users or groups an attacker enumerated, what changes a script made.
Prerequisites come before any incident: Microsoft Entra ID P1 or P2, and a diagnostic setting in Entra ID exporting MicrosoftGraphActivityLogs to a destination. The least-privileged role that can configure it is Security Administrator. Destinations follow the usual pattern: a Log Analytics workspace (including Sentinel's) for KQL investigation in the MicrosoftGraphActivityLogs table, a storage account for long-term archive, or Event Hubs for an external SIEM. Logs usually arrive within 30 minutes. Diagnostic settings can't filter events, so high-volume tenants control cost with workspace transformations or a lower-cost table plan.
Investigation uses KQL against the table. Typical hunts:
MicrosoftGraphActivityLogs
| where TimeGenerated > ago(7d)
| where UserId == "<compromised-user-object-id>"
| summarize Requests = count() by RequestMethod, tostring(parse_url(RequestUri).Path), AppId
| order by Requests desc
This shows what a compromised user's tokens were used for, by app and API path. Joining on SignInActivityId to the sign-in tables' UniqueTokenIdentifier links each request to the sign-in that produced its token, distinguishing the real user's activity from a replayed token. Other common hunts look for 401 and 403 failure spikes (enumeration attempts), unusual write operations to role assignments or app credentials, and requests from service principals that normally don't call those APIs.
⚠️ Exam Trap: "Investigate which Graph API calls a malicious app made last month" fails if the diagnostic setting wasn't configured beforehand; there is no backfill. The preventive answer is to export Graph activity logs to a Log Analytics workspace as part of normal setup, not to enable them after the incident.
Reflection Question: A sign-in log shows a service principal authenticating normally every hour. What extra questions can Graph activity logs answer about it, and why does that matter for detecting a compromised app credential?