Microsoft configuration#

Prerequisites#

Create an app registration in App Registrations in the Azure portal.

Authentication#

Create a client secret for the app registration. Store the secret in an environment variable and note the Microsoft tenant ID and application client ID.

Application authentication is the recommended mode because it provides explicit, repeatable permissions and complete cleanup semantics.

Required permissions#

Grant the app registration these Microsoft Graph application permissions:

  • AdministrativeUnit.Read.All: Read all administrative units.

  • Application.Read.All: Read all applications.

  • Directory.Read.All: Read directory data.

  • Group.Read.All: Read all groups.

  • GroupMember.Read.All: Read all group memberships.

  • User.Read.All: Read all users’ full profiles.

Optional permissions#

Grant these application permissions when ingesting the indicated data:

  • DeviceManagementManagedDevices.Read.All: Intune managed devices and detected apps.

  • DeviceManagementConfiguration.Read.All: Intune device configuration and compliance policies.

  • RoleManagement.Read.Directory: Entra directory role definitions and assignments.

  • AuditLog.Read.All: Entra user sign-in activity and MFA registration; also requires an Entra ID P1 or P2 license.

  • IdentityRiskyUser.Read.All: Entra ID Protection user risk; also requires an Entra ID P2 license.

  • IdentityRiskyServicePrincipal.Read.All: Entra ID Protection service principal risk; also requires a Workload Identities Premium license.

User sign-in activity#

Cartography requests signInActivity with the existing user inventory query, using pages of up to 500 users and no per-user requests. If the first request returns 403 Forbidden, it logs a warning and retries the inventory without activity. Failure of that retry or any later page still propagates and prevents cleanup. A successful fallback sets sign_in_activity_available to false and clears the activity timestamps, since the sync could not observe them. It still refreshes basic user inventory and allows its normal cleanup. Availability describes the request, not whether each user has signed in. A successful activity-enabled request sets it to true even for users with no activity record; their timestamps are null.

The three activity properties on EntraUser are native UTC datetimes:

  • last_successful_sign_in_date_time: last successful interactive or non-interactive sign-in. Use this for inactivity queries.

  • last_sign_in_date_time: last interactive attempt, including failures.

  • last_non_interactive_sign_in_date_time: last non-interactive attempt, including failures.

For example, find enabled accounts with a known successful sign-in older than 90 days:

MATCH (u:EntraUser)
WHERE u.account_enabled = true
  AND u.sign_in_activity_available = true
  AND u.last_successful_sign_in_date_time < datetime() - duration('P90D')
RETURN u.user_principal_name, u.last_successful_sign_in_date_time
ORDER BY u.last_successful_sign_in_date_time

When sign_in_activity_available is true, a null timestamp means Graph has no recorded sign-in for that user, not proof that the account never signed in. Microsoft does not backfill lastSuccessfulSignInDateTime before December 1, 2023. To review enabled accounts with no recorded successful sign-in, separately from accounts with a known sign-in older than 90 days:

MATCH (u:EntraUser)
WHERE u.account_enabled = true
  AND u.sign_in_activity_available = true
  AND u.last_successful_sign_in_date_time IS NULL
RETURN u.user_principal_name

This is a review list, not proof of 90 days of inactivity: it can include newly created accounts and accounts without backfilled history. Graph can omit signInActivity entirely for users who never signed in or last signed in before April 2020.

UserAccount._ont_lastactivity maps to last_successful_sign_in_date_time, a native datetime that is null whenever activity could not be read. Other providers may use different timestamp representations, so normalize them before comparing.

These timestamps measure sign-in recency, not usage frequency or active human use; background clients can produce successful non-interactive sign-ins.

See Microsoft’s signInActivity reference and inactive-account guidance.

MFA registration and identity risk#

Cartography reads three optional reports once per sync and merges them into the inventory:

Report

Permission

License

Properties

/reports/authenticationMethods/userRegistrationDetails

AuditLog.Read.All

Entra ID P1 or P2

EntraUser.is_mfa_registered, is_mfa_capable, mfa_methods_registered

/identityProtection/riskyUsers

IdentityRiskyUser.Read.All

Entra ID P2

EntraUser.risk_level, risk_state, risk_detail, risk_last_updated_date_time

/identityProtection/riskyServicePrincipals

IdentityRiskyServicePrincipal.Read.All

Workload Identities Premium

The same risk properties on EntraServicePrincipal

If a report returns 403 Forbidden, Cartography logs a warning, clears its properties, and still syncs users and service principals. Other errors propagate. Clearing follows the sign-in activity behavior above: a value the sync could not observe is not kept, so a revoked permission cannot leave a stale atRisk state in place. Each report has an availability flag that is false when it was denied, so a null property then means unknown rather than absent:

  • EntraUser.mfa_registration_available for the registration report

  • EntraUser.risk_available for the riskyUsers report

  • EntraServicePrincipal.risk_available for the riskyServicePrincipals report

When a risk report was read, users and service principals that are not in it have null risk properties and risk_available set to true.

EntraUser.user_type (Member or Guest) and external_user_state come from the base user inventory and need no extra permission.

The registration report lags by up to 36 hours and leaves out disabled users, so their MFA properties stay null. It also leaves out soft-deleted users, which the user inventory doesn’t include either.

Federated domains#

The registration report only covers methods registered in Entra ID. A user whose domain is federated to another identity provider, such as AD FS or Okta, completes MFA there, so the report shows no methods for them. For that reason is_mfa_registered is not mapped to UserAccount._ont_has_mfa.

Cartography syncs the tenant’s domains as EntraDomain nodes, using the Directory.Read.All permission it already requires, and links each user to the domain of its user principal name with (:EntraUser)-[:IN_DOMAIN]->(:EntraDomain). authentication_type is Managed when Entra ID signs the domain’s users in and Federated when another identity provider does.

For example, find enabled members of managed domains with no registered MFA:

MATCH (u:EntraUser)-[:IN_DOMAIN]->(d:EntraDomain {authentication_type: 'Managed'})
WHERE u.account_enabled = true
  AND u.user_type = 'Member'
  AND u.mfa_registration_available = true
  AND u.is_mfa_registered = false
RETURN u.user_principal_name, d.name

Defender for Endpoint#

Defender for Endpoint machines and unresolved alerts sync by default when the tenant grants access. To include them, grant admin consent for these application permissions on the same app:

Dataset

API

Permission

Data

defender_machines

WindowsDefenderATP

Machine.Read.All

Defender for Endpoint machine inventory

defender_alerts

Microsoft Graph

SecurityAlert.Read.All

Unresolved Defender for Endpoint alerts

Machine inventory covers the tenant’s configured Defender retention period. Only unresolved Defender for Endpoint alerts are ingested; resolved alerts and alerts outside the retention window are removed on the next successful sync. Repeated records or pagination cursors mean the provider’s data changed mid-read, so the sync aborts before writes; rerun once those changes settle.

Each dataset is skipped, without cleanup, when the tenant can’t provide it: Defender for Endpoint isn’t provisioned (token error AADSTS500011), its permission is missing (401 or 403), or, for machines, Microsoft returns 404 because there are no recently seen machines. A skip logs its cause, naming the permission to grant when a request is denied, and preserves previously synced Defender data, like the optional Entra permissions above. The connector uses the commercial cloud endpoints and does not support sovereign cloud endpoints. Machine tokens use https://api.securitycenter.microsoft.com/.default, as required by Microsoft even though requests go to https://api.security.microsoft.com. Alert tokens use https://graph.microsoft.com/.default. Both are obtained through the existing Microsoft service principal credential. No write API permissions are required.

Configure Cartography#

Provide these options:

  • --microsoft-tenant-id: Microsoft tenant ID.

  • --microsoft-client-id: App registration client ID.

  • --microsoft-client-secret-env-var: Name of the environment variable containing the client secret.

These credentials apply to all ingestion in the microsoft module, including Entra ID, Intune, O365, and Defender for Endpoint ingestion.

The deprecated --entra-tenant-id, --entra-client-id, and --entra-client-secret-env-var aliases remain accepted until Cartography v1.0.0. Do not mix --microsoft-* and --entra-* credential flags in one invocation.

Run Cartography#

export MICROSOFT_CLIENT_SECRET='<client-secret>'
cartography \
  --selected-modules microsoft \
  --microsoft-tenant-id '<tenant-id>' \
  --microsoft-client-id '<client-id>' \
  --microsoft-client-secret-env-var MICROSOFT_CLIENT_SECRET

Defender machine inventory requests use pages of up to 10,000 records; Microsoft documents limits of 100 machine requests per minute and 1,500 per hour. Alerts use Graph /v1.0/security/alerts_v2 with serviceSource eq 'microsoftDefenderForEndpoint', one query each for the unresolved statuses new and inProgress, pages of 100, and @odata.nextLink pagination. Requests retry HTTP 429 and transient 5xx responses up to five times and cap each Retry-After delay at eight seconds. Each collection is limited to 10,000 pages; exceeding that limit aborts the snapshot before graph writes or cleanup.

Sync selected resources#

Use --microsoft-requested-syncs to sync only some Microsoft resources, for example to isolate a slow or failing dataset in a large tenant:

cartography \
  --selected-modules microsoft \
  --microsoft-tenant-id '<tenant-id>' \
  --microsoft-client-id '<client-id>' \
  --microsoft-client-secret-env-var MICROSOFT_CLIENT_SECRET \
  --microsoft-requested-syncs users,groups

Valid values are domains, users, groups, administrative_units, applications, service_principals, app_role_assignments, directory_roles, federation, intune, o365, defender_machines, and defender_alerts. Intune and O365 run as whole units. The Entra tenant always syncs because every other Microsoft node relates to it.

A dataset that isn’t requested is neither synced nor cleaned up, so its existing graph data stays as it was. Relationships to resources that aren’t requested are created only when those resources are already in the graph, so run the full sync at least once before syncing selected resources.

Experimental delegated user authentication#

Use delegated authentication only when you can’t use an app registration and you need a best-effort snapshot of the Entra data visible to a signed-in user. Delegated authentication doesn’t grant the user additional Microsoft Graph permissions. It isn’t a replacement for application authentication.

Limitations#

  • Only Entra datasets are attempted. Intune, O365, and Defender ingestion are skipped.

  • Microsoft Graph may return partial results without an authorization error.

  • If Microsoft Graph returns 403 Forbidden, Cartography stops the affected dataset, reports it, and continues with the next dataset. After attempting all datasets, Cartography exits with a nonzero status so automation can’t mistake the partial inventory for a complete sync. Records from earlier pages of the affected dataset can remain in the graph.

  • Because the command exits with a nonzero status after a denied dataset, run delegated Microsoft ingestion separately from other selected modules.

  • If Microsoft Graph returns 401 Unauthorized, or an error other than 403, Cartography stops the run.

  • Cartography disables cleanup and derived federation analysis. A delegated run doesn’t delete existing Entra data, so records that the user can’t see can remain in the graph.

  • With --microsoft-delegated-auth alone, Cartography reads the local Azure CLI token cache. Use that only for an attended, one-time run on a trusted workstation. Don’t use it for hosted or unattended inventory collection.

  • With --microsoft-access-token-env-var, the token can’t be refreshed, and the sync fails once it expires. Token lifetime depends on the issuing client and tenant policy; Cartography logs the remaining lifetime when the sync starts. Mint the token immediately before the run.

A run that reports no denied datasets can still be incomplete. Microsoft Graph can filter results based on the signed-in user’s effective visibility without returning 403 Forbidden.

Sign in and run Cartography#

Use a dedicated, non-privileged test user and a fresh disposable Neo4j database when you evaluate this mode.

  1. Sign in to the target tenant. You don’t need an Azure subscription.

    az login --tenant '<TENANT_ID>' --allow-no-subscriptions
    

    If a browser can’t open in your environment, add --use-device-code. Your tenant’s Conditional Access policy might not allow device-code authentication.

  2. Run Cartography.

    cartography \
      --selected-modules microsoft \
      --microsoft-tenant-id '<TENANT_ID>' \
      --microsoft-delegated-auth
    

Do not pass --microsoft-client-id or --microsoft-client-secret-env-var with delegated authentication.

Run with an access token#

Instead of the local Azure CLI session, Cartography can use a pre-issued Microsoft Graph access token, for example one minted on another machine or by your own delegated sign-in flow. --microsoft-access-token-env-var implies --microsoft-delegated-auth, with the same limitations.

The token must be issued for Microsoft Graph (https://graph.microsoft.com) and for the tenant passed in --microsoft-tenant-id. An Azure Resource Manager token is rejected: use --azure-access-token-env-var for the Azure module.

az login --tenant '<TENANT_ID>' --allow-no-subscriptions
export MICROSOFT_ACCESS_TOKEN="$(az account get-access-token \
  --resource https://graph.microsoft.com \
  --query accessToken --output tsv)"

cartography \
  --selected-modules microsoft \
  --microsoft-tenant-id '<TENANT_ID>' \
  --microsoft-access-token-env-var MICROSOFT_ACCESS_TOKEN

Troubleshooting#

If Defender machines or alerts are missing, check the sync log for a skipped Defender dataset and its cause. A denied request (401 or 403) names the permission to grant; admin consent must be granted again after adding a permission to the app. AADSTS500011 means the tenant has no Defender for Endpoint, and a machine 404 means Defender reports no recently seen machines. An exhausted retry, malformed page, or incomplete pagination aborts the Defender sync before graph writes or cleanup, preserving previous Defender data. A successful empty collection is eligible for cleanup.

References#