Skip to main content

Collecting Microsoft 365 and Entra logs

The highest-value log source: how it is configured, which activity feeds we read, why Audit.AzureActiveDirectory is the one that matters, and its honest status.

Written for whoever runs IT9 min readUpdated

If you collect one log source, collect this one. For an organisation without a security team, almost every real incident begins as an identity event — a phished password, an inbox rule forwarding mail to a stranger, an application granted permission to read everyone's mail. Those events exist in the Microsoft audit feeds and, for most small organisations, nowhere else.

This article explains what is read, how it is configured, and what its status honestly is. Two limits belong at the top because they change how you should use this source.

Which activity feeds are read

Microsoft splits its unified audit log into content types. A subscription is started per content type, and each is paged independently. Five are recognised by the platform; three are pulled unless you ask for something else.

The Microsoft content types the platform understands, and whether each is pulled by default.
Content typeIn the default setWhat it carries
Audit.AzureActiveDirectoryYes — firstEntra ID sign-ins, consent grants, risky-user events and directory-role changes. The identity feed.
Audit.ExchangeYesMailbox activity, including inbox rule creation, delegate changes and mailbox permission grants.
Audit.GeneralYesWorkloads without a feed of their own — Teams, Power Platform, and a long tail of service activity.
Audit.SharePointNoSharePoint and OneDrive file and sharing activity. High volume. Available on request.
DLP.AllNoData loss prevention rule matches. Shipped and selectable, deliberately not on by default.

The default set is ordered so identity telemetry is fetched first. That ordering is not cosmetic. If Microsoft rate-limits us part way through a poll, losing SharePoint file reads costs far less than losing sign-ins, and the order of the list is what decides which one you lose.

Why Audit.General is not enough on its own

The name suggests a catch-all. It is not one. Audit.General explicitly excludes Audit.AzureActiveDirectory, which means a configuration subscribed only to Audit.General will never see a single Entra ID sign-in, consent grant or role assignment — no matter how correct everything downstream is.

This is worth spelling out because it is exactly the mistake an earlier revision of this platform made, and the symptom was not an error. Collection ran, records arrived, dashboards showed traffic, and the identity events that matter were simply absent. If you are auditing your own configuration, the one thing to verify is that Audit.AzureActiveDirectory is in your content type list.

How it is configured

You register an application in your own tenant, grant it the Management Activity API read permissions, and give us the details. We create the receiver configuration.

  1. Register an application in Entra ID

    A single-tenant application registration is enough. Note the directory (tenant) ID and the application (client) ID.

  2. Add a client secret

    Authentication is the OAuth client-credentials grant against https://login.microsoftonline.com, requesting the scope https://manage.office.com/.default. There is no user in the flow and no interactive consent prompt at poll time.

  3. Grant the Management Activity API read permissions and consent

    The application needs permission to read your organisation's activity data, granted as an application permission with tenant administrator consent. Reading is all it can do; there is no write surface on this API.

  4. Send us the tenant ID, client ID and the secret reference

    In production the secret is referenced, not stored: the configuration holds the name of an environment variable and the worker resolves it at runtime. An inline secret is accepted so a local development stack works, and the worker logs a warning when it finds one outside development. This is a deliberate stop-gap, recorded as such — there is no application-level secret store yet, and building one inside the log ingest package would have been the wrong place for it.

  5. Confirm unified audit logging is on in your tenant

    If Microsoft is not recording the audit log, there is nothing to poll and no error to see. This is a tenant setting on your side, not ours.

The shape of the stored configuration, with the secret referenced rather than held
{
  "tenant_id": "3f9a...",
  "client_id": "b71c...",
  "client_secret_env": "ZQ_M365_SECRET_acme",
  "content_types": ["Audit.AzureActiveDirectory", "Audit.Exchange", "Audit.General"]
}

How the pull works, and why it is three requests

The Management Activity API is not a simple give me events since call, and an implementation that treats it as one returns nothing. There are three stages.

  1. Start a subscription for each content type. Without it, listing content returns the error AF20022 with the message No subscription found for the specified content type. Microsoft throttles a repeated start for the same content type to one every fifteen minutes, so which content types have been started is recorded against the configuration and re-asserted about once a day rather than on every poll.
  2. List content for a time window. This returns descriptors — a content id, a content URI and a creation time — not events. Pagination is by a NextPageUri response header, not a field in the body, which is an easy detail to miss.
  3. Fetch each content URI, which returns the JSON array of audit records.

Microsoft imposes two bounds the platform respects: a listing window may be at most 24 hours, and its start may be at most 7 days in the past. A tenant that has been unreachable for longer than a week has permanently lost the older records, and no configuration on our side can recover them.

One poll is deliberately bounded — at most eight windows and fifty pages per window — so a tenant catching up after an outage does so over several ticks instead of monopolising the worker that serves every other tenant in the same cell. Default cadence is five minutes, adjustable per source.

Why the position is not just a timestamp

Microsoft documents explicitly that records inside a content blob are not ordered, and that a blob can contain events older than an earlier blob's. A plain last seen timestamp therefore either skips events or re-reads them. The position kept here is a creation-time watermark plus the set of content ids already consumed within the current window. Combined with a uniqueness constraint on the stored event, a blob re-read after a crash lands as a no-op rather than double-counting — which matters, because a duplicated brute-force burst is the difference between a real threshold being met and a fictional one.

What failure looks like

Failures are per source and never fatal to the worker. A configuration that cannot be used at all is not scheduled, with one loud message at start-up rather than a silent absence. A source that fails at runtime backs off exponentially, with jitter, up to a cap — a revoked secret does not sit in a tight retry loop, and it does not stop any other tenant collecting.

Messages worth recognising
m365 throttled: <detail>
m365 status=<code> body=<detail>
m365: authentication failed after token refresh
subscriptions/start <contentType>: status=<code> body=<detail>
token response contained no access_token
siem ingest: receiver skipped — invalid configuration
siem ingest: puller not scheduled — invalid configuration

One guard is worth naming because it looks like a bug when it fires. The content URIs in stage two are supplied by the remote API, and blindly fetching a URL a remote service hands you is a server-side request forgery waiting to happen. A content URI pointing at a host other than the expected one is refused: refusing to fetch contentUri on foreign host. If you see it, the cause is a misconfigured base URL rather than an attack, but the refusal is correct either way.

What to do about the alerting gap in the meantime

Given that this source does not currently drive alerts, here is what we would actually advise, in the order the value falls.

  • Run the posture assessment and act on it. Almost every identity finding a detection rule would tell you about after the fact — an administrator using their admin account for daily email, an application with tenant-wide mail read, no second factor on a director — the assessment finds before the fact. Prevention beats detection you do not yet have.
  • Leave Microsoft's own alerting on. If your licence includes Entra ID Protection or Defender alerts, do not switch them off in anticipation of ours. Nothing here replaces them today.
  • Collect anyway. Retained, searchable, normalised audit history is what makes an investigation possible three weeks later, and it is what an auditor asks for. It has value independent of alerting, and it means the history is already there when the join lands.

What to read next

  1. How your logs are normalised — what a Microsoft audit record turns into, and which fields survive.
  2. Searching your logs — how to confirm your own sign-ins are arriving.
  3. Log retention — how long this history is kept.
  4. Connecting your first log source — the per-source status table for everything else.

Was this article wrong?

If a procedure here does not match what you see, or a limit we described has changed, tell us and we will fix the page. Email us about this article, or see how to get help if you need an answer rather than a correction.

Everything in logs and detection