Skip to main content

Microsoft Entra ID

Overview​

Microsoft Entra ID (formerly Azure Active Directory) is a pull-based source that fetches your tenant's directory from Microsoft Graph as identity context. You choose which kinds of objects to collect:

DataMicrosoft Graph APIDefaultGoogle SecOps log type
Users: every user's profile, account status, department, on-premises identifiers and managerGET /beta/users?$expand=managerOnAZURE_AD_CONTEXT
Groups: every group's name, type and settings (without members)GET /beta/groupsOffnone (see below)
Devices: every registered device, with its ownersGET /beta/devices?$expand=registeredOwnersOffnone (see below)
Admin roles: every active admin role assignment, meaning who (user, group or app) holds which Entra role, such as Global AdministratorGET /beta/roleManagement/directory/roleAssignments?$expand=principalOffnone (see below)

The user call is the same one the Google SecOps native Azure AD Organizational Context feed makes. Every object is sent exactly as Graph returns it, so the Google SecOps AZURE_AD_CONTEXT parser receives the complete user record. This is not an event stream: it is a periodic snapshot of the directory that Google SecOps uses to enrich other logs with who a user is.

Groups, devices and admin roles are not understood by Google SecOps today. The AZURE_AD_CONTEXT parser maps users only, and there is no Google SecOps log type for Entra ID groups, devices or role assignments. Collect them for other destinations or for search, and never label them AZURE_AD_CONTEXT (see Sending to Google SecOps).

Admin roles use Microsoft's recommended role-management API rather than /directoryRoles, which lists only role names, not who holds them. Only active assignments are included; PIM eligible assignments (people who can activate a role) are not.

Sign-in logs (AZURE_AD) are not collected by this source. Use the Azure Event Hub source with an Entra ID diagnostic setting that exports SignInLogs (and NonInteractiveUserSignInLogs, ServicePrincipalSignInLogs, ManagedIdentitySignInLogs as needed).

Supported types: Logs

Supported platforms: Linux, Windows, macOS

Minimum collector version: 0.5.62

Authentication​

Attach an Azure Auth (azureauth) credential, the shared Azure identity credential also used by the Azure Event Hub source and the Microsoft Sentinel destination. The node holds no secret.

The identity behind the credential needs one Microsoft Graph application permission per kind of data you collect:

PermissionTypeNeeded for
User.Read.AllApplicationUsers (on by default)
Group.Read.AllApplicationGroups
Device.Read.AllApplicationDevices
RoleManagement.Read.DirectoryApplicationAdmin roles

Set up an app registration (service principal)​

  1. In the Microsoft Entra admin center, go to Identity → Applications → App registrations → New registration. Choose Accounts in this organizational directory only and leave the redirect URI empty.
  2. Copy the Application (client) ID and Directory (tenant) ID.
  3. Under API permissions, choose Add a permission → Microsoft Graph → Application permissions, then add User.Read.All, plus Group.Read.All, Device.Read.All and/or RoleManagement.Read.Directory if you collect groups, devices or admin roles.
  4. Click Grant admin consent for your organization. This needs the Global Administrator or Privileged Role Administrator role. The status column must show Granted for every permission.
  5. Under Certificates & secrets, create a client secret and copy its Value.
  6. In Praxis, create an azureauth credential with Auth Type service_principal and the tenant ID, client ID and secret.

They must be application permissions, not delegated ones. The collector signs in as the app itself, so a delegated permission grants nothing and every request fails with 403 Authorization_RequestDenied.

Set up a managed identity (collector running in Azure)​

A managed identity stores no secret, but works only when the collector runs on an Azure resource (VM, VM scale set, AKS node, Container Apps) that the identity is assigned to.

azureauth Auth TypeUse whenCredential fields
system_managed_identityThe VM/scale set has its own identity (Identity → System assigned → On)none
user_managed_identityYou created a Managed Identity resource and assigned it to the VM/scale setthe identity's Client ID
workload_identityThe collector runs in AKS with workload identitytenant ID, client ID, federated token file (usually /var/run/secrets/azure/tokens/azure-identity-token)

The identity needs the same Microsoft Graph application permissions as an app registration, but the Entra admin center cannot grant them to a managed identity. An administrator with Privileged Role Administrator or Global Administrator assigns them with Microsoft Graph PowerShell, using the identity's Object (principal) ID. That ID is shown on the VM's Identity page for system-assigned identities, and on the Managed Identity resource's Overview page for user-assigned ones.

Connect-MgGraph -Scopes "Application.Read.All","AppRoleAssignment.ReadWrite.All"
$miObjectId = "<managed identity object (principal) ID>"
$graph = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'"
# Keep only the permissions for the data you collect.
foreach ($perm in "User.Read.All","Group.Read.All","Device.Read.All","RoleManagement.Read.Directory") {
$role = $graph.AppRoles | Where-Object { $_.Value -eq $perm -and $_.AllowedMemberTypes -contains "Application" }
New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $miObjectId -PrincipalId $miObjectId -ResourceId $graph.Id -AppRoleId $role.Id
}

To check the result, open Enterprise applications, set the Application type filter to Managed Identities, select the identity, and open Permissions.

To test from the Azure VM itself before deploying (add &client_id=<client ID> to the URL for a user-assigned identity):

TOKEN=$(curl -s -H Metadata:true "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://graph.microsoft.com/" | python3 -c 'import sys,json; print(json.load(sys.stdin)["access_token"])')
curl -s -H "Authorization: Bearer $TOKEN" 'https://graph.microsoft.com/beta/users?$top=1' | head -c 400

Newly granted permissions can take several hours to reach a managed identity. Azure caches managed identity tokens. A 403 straight after granting a permission usually clears on its own; the source keeps retrying.

Configuration​

Data to collect​

ParameterTypeDefaultDescription
users.enabledbooltrueCollect users.
groups.enabledboolfalseCollect groups. Needs Group.Read.All.
devices.enabledboolfalseCollect devices. Needs Device.Read.All.
roles.enabledboolfalseCollect admin role assignments. Needs RoleManagement.Read.Directory.

At least one must be on. Each kind is fetched and tracked independently: if, say, the app lacks Group.Read.All, groups fail and retry on their own while users keep flowing.

Schedule​

One schedule applies to every kind you collect.

ParameterTypeDefaultDescription
schedule.modescheduled | manualscheduledscheduled: fetch everything every schedule.interval. manual: fetch only with Fetch Now.
schedule.intervalinteger (seconds)86400Time between snapshots in scheduled mode. Minimum 900 (15 minutes).
schedule.sync_requestinteger0Fetch Now: the on-demand trigger, shown and used in manual mode only. See below.

Options​

ParameterTypeDefaultDescription
users.expand_managerbooltrueEmbed each user's manager. Google SecOps maps it to the user's managers.
devices.expand_ownersbooltrueEmbed each device's registered owners (up to 20), linking a device to its users.
persist_offsetsbooltruePersist Fetch State. Remember when each type was last fetched and which Fetch Now was completed, so restarts don't fetch everything again. See Fetch state.

advanced​

ParameterTypeDefaultDescription
users.selectlist of stringsnoneGraph user properties to fetch ($select). Leave empty to get Graph's default set, which is what Google SecOps expects. When set, Graph returns only these properties. Use it to add directory extension attributes (extension_<appId>_<name>). Properties Graph only returns for a single user (aboutMe, birthday, hireDate, interests, mailboxSettings, mySite, pastProjects, preferredName, responsibilities, schools, skills) and signInActivity are rejected when you save.
api_versionbeta | v1.0betaGraph API version. beta matches the Google SecOps native feed.
page_sizeinteger999Users requested per Graph call (1–999). Graph may return fewer per page when expanding managers; the source always follows the next-page link.
timeoutinteger (seconds)60HTTP timeout for requests to Microsoft Graph.
tls.insecure_skip_verifyboolfalseDisable TLS server certificate verification. Not recommended in production.
tls.ca_filestringnoneCustom CA bundle, for example for a TLS-inspecting proxy.

In the pipeline editor, User Properties (users.select) appears inside the Advanced group, but it is a key of the users block.

Fetching on demand​

Fetch Now appears only when Fetch Mode is manual, and in that mode it is the only way data is fetched.

  1. Set Fetch Mode to manual.
  2. In Fetch Now, enter a number higher than the current one (for example 1 → 2) and deploy. Every kind you collect is fetched once.

Fetch state​

With Persist Fetch State on (the default), the collector stores two small values per type of data (users, groups, devices, admin roles) under <collector home>/metadata/entra_id_offsets/<node name>:

Stored valueExampleUsed for
Time the last complete fetch finished2026-10-09T07:15:00ZAfter a restart, wait until that time plus the interval instead of fetching now.
Last Fetch Now value completed5After a restart, do not repeat a Fetch Now that already ran.

No records and no position in the directory data are stored. Every fetch reads the whole directory from the first page, so the order in which Microsoft Graph returns objects does not matter. If a fetch fails part-way, the page to continue from is kept in memory only and is dropped on restart, when Graph rejects it, or after 3 failed attempts; the next attempt then starts from the first page.

Turn Persist Fetch State off only on collectors that cannot write to disk (read-only filesystem, ephemeral container without a volume). Without it, every collector restart (config push, upgrade, reboot) fetches the whole directory again, and in manual mode repeats the last Fetch Now. If the stored files are deleted, the next restart simply fetches once.

Sending to Google SecOps​

The source does not set a Google SecOps log type itself. Add a Google SecOps Standardization processor between this source and the Google SecOps destination with:

FieldValue
Log TypeAZURE_AD_CONTEXT
Conditionresource.attributes["entraid.source"] == "users"

The condition is required whenever groups, devices or admin roles are on: without it, those records would be labeled AZURE_AD_CONTEXT and fed to a parser that only understands users. If you also send to Google SecOps, either drop non-user records before the SecOps destination (for example with a filter on resource.attributes["entraid.source"] != "users") or route them to a different destination.

Output mapping​

Every object becomes one log record. All records carry:

SourceOTel destination
(whole object JSON, as Graph returned it)LogRecord.Body (string)
(snapshot start time)LogRecord.Timestamp: the record describes the object as of that snapshot
(constant)Resource.Attributes["vendor"] = "microsoft"
(kind of object)Resource.Attributes["entraid.source"] = users, groups, devices or roles
(Graph host)Resource.Attributes["entraid.graph_host"]

Promoted attributes, each only when present:

KindGraph field → attribute
Usersid → entraid.user.id, userPrincipalName → entraid.user.principal_name, userType → entraid.user.type, accountEnabled → entraid.user.account_enabled (bool)
Groupsid → entraid.group.id, displayName → entraid.group.display_name, securityEnabled → entraid.group.security_enabled (bool), mailEnabled → entraid.group.mail_enabled (bool)
Devicesid → entraid.device.id, deviceId → entraid.device.device_id, displayName → entraid.device.display_name, operatingSystem → entraid.device.operating_system, accountEnabled → entraid.device.account_enabled (bool)
Admin rolesid → entraid.role_assignment.id, roleDefinitionId → entraid.role.definition_id, (role name, looked up) → entraid.role.name, directoryScopeId → entraid.role.directory_scope_id, principalId → entraid.principal.id, principal type → entraid.principal.type (user, group, servicePrincipal), principal.displayName → entraid.principal.display_name, principal.userPrincipalName → entraid.principal.user_principal_name

The promoted attributes exist for routing and filtering. The body is untouched, so a downstream parser still sees the complete record.

Example configuration​

{
"users": { "enabled": true, "expand_manager": true }, // default: on
"groups": { "enabled": true }, // default: off
"devices": { "enabled": false, "expand_owners": true }, // default: off
"roles": { "enabled": true }, // default: off
"schedule": {
"mode": "scheduled", // default: scheduled
"interval": 86400, // default: 86400, minimum 900
"sync_request": 0 // manual mode: raise and deploy to fetch everything now
},
"persist_offsets": true, // default: true
"advanced": {
"api_version": "beta", // default: beta
"page_size": 999, // default: 999
"timeout": 60 // default: 60
}
}

Operational notes​

  • Volume. Each snapshot sends one record per object: a daily snapshot of a tenant with 50,000 users, 3,000 groups, 20,000 devices and 200 admin role assignments is about 73,200 records a day with everything on.
  • Admin role names. Each admin-roles snapshot first reads the role definitions (one or two extra calls) to add the role name to each record.
  • Switching tenants. Fetch state is stored per node name, not per tenant. If you point an existing node's credential at a different tenant, its first fetch can wait up to one interval. Switch to manual and raise Fetch Now to fetch the new tenant immediately.
  • Partial snapshots. Pages are sent downstream as they arrive. If a snapshot fails part-way (for example Graph keeps returning errors), the next attempt resumes at the page that failed, so objects already sent are not sent again. Only if the collector restarts in between does the snapshot start over.
  • Retries. Throttling (429) and server errors (5xx) are retried up to 5 times, honoring Graph's Retry-After. A failed snapshot is retried after 5 minutes, then with doubling waits up to 1 hour.
  • Graph throttling budget. Each list call costs 1–3 resource units per page against a per-app, per-tenant budget of 3,500–8,000 units per 10 seconds, so even large tenants snapshot everything well inside the limit.
  • Group membership is not collected. Listing each group's members costs one extra Graph call per group.
  • Conditional Access. If a Conditional Access policy restricts workload identities by location, exclude the collector's egress IPs (or this app's service principal) from it.
  • Sovereign clouds (US Government, China) are not supported yet.

Troubleshooting​

Symptom in the collector logCauseFix
entra id <kind> snapshot failed … graph returned 403 Authorization_RequestDenied, with a hint naming a permissionThe app lacks that kind's application permission (User.Read.All, Group.Read.All, Device.Read.All or RoleManagement.Read.Directory), or admin consent was not granted.Add the application permission and click Grant admin consent.
get Entra ID token: … (for example AADSTS7000215: Invalid client secret), with a hint to check the credentialService principal: wrong tenant ID or client ID, the secret's ID was entered instead of its Value, or the secret expired.Fix or rotate the azureauth credential.
get Entra ID token: … ManagedIdentityCredential: …Managed identity: the collector is not running on an Azure resource, the identity is not assigned to that resource, or (user-assigned) the client ID is wrong.Assign the identity to the VM / scale set / container, or fix the client ID.
graph returned 401 InvalidAuthenticationTokenA token was obtained but Graph rejected it, typically a token for the wrong tenant.Check the credential's tenant ID.
No records and no errors in manual modeFetch Now was not raised above the value of the last completed fetch.Enter a higher number and deploy.

Metrics​

All kinds report under the same node.

Metric NameDescription
prx_src_records_received_total{signal="logs"}Log records emitted, one per user, group, device or role assignment.
prx_src_bytes_received_totalResponse bytes read from Microsoft Graph.
prx_src_pipeline_latency_secondsArrival to downstream handoff, one sample per page.
prx_src_errors_total{reason="io"}Graph request failures, including permission errors and exhausted retries.
prx_src_parse_errors_total{parser="json"}A Graph response that was not a list, or objects whose fields had unexpected types (those objects are still sent).
prx_src_records_dropped_total{reason="backpressure"}Records dropped because the downstream pipeline refused them.