At a glance

VendorMicrosoft Entra ID (formerly Azure Active Directory)
Source typeidentity
Vendor ID (slug)entra-id
Base URLhttps://graph.microsoft.com/v1.0 — commercial cloud. US Gov / China / Germany customers override this manually.
Auth methodoauth2 — client-credentials flow. Draxis mints an Authorization: Bearer <access_token> per run via https://login.microsoftonline.com/<tenant_id>/oauth2/v2.0/token.
Schedule defaultdaily — override to hourly if you want tighter response on failed-login and role-change KRIs.
LicensingEntra ID Free / P1 / P2 all work for most KRIs. Sign-in logs and Conditional Access APIs require P1 or higher; the connector tolerates 403 on those and records the affected KRIs as max-gap (fail-safe).
AvailabilityNew in 2026.04.

Required scopes & roles

Draxis authenticates as an Entra ID app registration using the client-credentials flow — no user is impersonated. The app needs these Microsoft Graph Application permissions (all Read.All, all requiring admin consent):

  • User.Read.All — list users with userType, accountEnabled, and signInActivity. Backs GET /users.
  • Directory.Read.All — list activated directory roles and their members (privileged-account enumeration). Backs GET /directoryRoles and GET /directoryRoles/{id}/members.
  • AuditLog.Read.All — read sign-in logs and directory audits (failed sign-ins, role-management events). Required even for signInActivity on /users. Backs GET /auditLogs/signIns and GET /auditLogs/directoryAudits.
  • Policy.Read.All — read Conditional Access policies to measure coverage gaps. Backs GET /identity/conditionalAccess/policies.
  • Reports.Read.All — read the aggregate authentication-methods registration report. Backs GET /reports/authenticationMethods/userRegistrationDetails (used instead of per-user calls for MFA enrollment — one paginated call vs. N calls).

Do not grant the app any ReadWrite.All variant, Directory.AccessAsUser.All, or Global Administrator role membership. The connector never writes to your directory.

Setup steps

  1. Register the app. In the Entra admin center go to Identity → Applications → App registrations → New registration. Name it Draxis Entra Reader. Supported account types: Single tenant. Redirect URI: leave blank (client-credentials flow doesn’t use one). Click Register.
  2. Copy the three IDs you’ll need. On the new app’s Overview page, copy:
    • Application (client) ID — you’ll paste this into Draxis’s Client ID field.
    • Directory (tenant) ID — you’ll paste this into Draxis’s Extra Config JSON.
    • (The third, Object ID, is not needed by Draxis.)
  3. Create a client secret. Go to Certificates & secrets → Client secrets → New client secret. Set a descriptive name (draxis-connector) and a rotation window that matches your policy (Microsoft caps at 24 months). Copy the secret Valuenot the Secret ID. You cannot retrieve this value later; if you lose it, create a new secret.
  4. Add the Graph permissions. Go to API permissions → Add a permission → Microsoft Graph → Application permissions. Search for and add each permission exactly:
    User.Read.All
    Directory.Read.All
    AuditLog.Read.All
    Policy.Read.All
    Reports.Read.All
    Do not add the Delegated variants — client-credentials flows can only use Application permissions.
  5. Grant admin consent. Back on the API permissions page, click Grant admin consent for <your tenant>. The status column should flip to a green checkmark for all five permissions. If this button is disabled, ask a Global Administrator or Privileged Role Administrator to perform the consent.
  6. (Optional) Restrict by IP. For extra hardening, go to Authentication → Conditional Access on the app and require requests to come from your corporate network or from the Draxis egress range. Skip this on first setup and add it after you’ve confirmed the connector works.

Wire it into Draxis

  1. Open Settings → Integrations in your tenant.
  2. Click Add integration and pick Identity Provider as the source type.
  3. Pick Microsoft Entra ID (Azure AD) from the vendor dropdown. Draxis auto-fills the Graph base URL, the OAuth auth method, the daily schedule, and seeds extra_config_json with {"tenant_id":""}.
  4. In Client ID, paste the Application (client) ID from step 2 above.
  5. In Client Secret, paste the secret Value from step 3. Draxis encrypts it server-side with encryption.key before storage.
  6. In the Extra Config JSON field, replace the empty tenant_id with your Directory (tenant) ID from step 2, e.g. {"tenant_id":"00000000-0000-0000-0000-000000000000"}.
  7. Click Test. Green means Draxis exchanged the credentials for an access token and read your tenant’s organization record successfully.
  8. Under KRIs to import, tick the KRIs you want Draxis to manage. All seven entra_* KRIs are checked by default; uncheck any you don’t need. Selected rows are created on save with the seeded thresholds (tunable later in the KRIs tab). Unchecking a previously-imported KRI deletes it on save.
  9. Save. The connector runs daily by default; use Run now from run history to trigger the first sync immediately.

KRIs produced

SlugMeaningDerivation
entra_mfa_enrollment_pct % of enabled, non-guest users registered for MFA round(count(enabled_members where userRegistrationDetails.isMfaRegistered == true) / count(enabled_members) * 100, 1)
entra_privileged_no_mfa_count Members of any activated directory role who are not MFA-registered count(distinct directoryRole_members where isMfaRegistered != true) — deduped across roles
entra_stale_accounts_90d Enabled, non-guest users with no sign-in in the last 90 days count(enabled_members where signInActivity.lastSignInDateTime < now - 90d OR signInActivity is null)
entra_ca_coverage_gaps Approximate count of users not covered by a universal MFA policy 0 if there exists at least one CA policy where state == 'enabled' AND conditions.users.includeUsers contains 'All' AND no excludeUsers AND grantControls.builtInControls contains 'mfa'; else count(enabled_members)
entra_guest_account_count Count of users with userType == 'Guest' count(users[].userType == 'Guest')
entra_failed_signins_24h Sign-ins with non-zero errorCode in the last 24 hours count(signIns where status.errorCode ne 0 and createdDateTime ge now - 24h)
entra_priv_role_changes_24h Role-management audit events in the last 24 hours count(directoryAudits where category == 'RoleManagement' and activityDateTime ge now - 24h)

Each row is a slug the connector writes to. Draxis creates the matching kri rows automatically when you check them in the KRIs to import section of the integration form — no manual API call or seed script needed. Thresholds shown in the table are the seeded defaults; you can edit them freely in the KRIs tab afterwards.

Vendor quirks

  • Licensing gates two KRIs. entra_failed_signins_24h and entra_ca_coverage_gaps require Entra ID P1 or P2 — Graph returns 403 on those APIs for Free tenants. The connector catches 403/404 and records failed_signins_24h = 0 (nothing to measure) and ca_coverage_gaps = count(enabled_members) (max gap — fail-safe). A run log warning tells you this happened.
  • Conditional Access coverage is approximated, not exhaustive. A truly exhaustive coverage check would resolve every policy’s includeUsers/includeGroups/includeRoles against excludeUsers/excludeGroups per user — a small policy engine. Draxis’s v1 heuristic answers the 95% question: do you have at least one enabled policy that applies to all users and requires MFA? If yes, coverage is treated as complete. If your posture depends on finer-grained CA modeling, open a support request — a full resolver is on the roadmap.
  • signInActivity needs AuditLog.Read.All. Even though you’re only reading the /users endpoint, Graph hides signInActivity unless the caller holds that permission. Missing it makes entra_stale_accounts_90d spike to the full enabled-member count on the first run.
  • Tenant ID goes in Extra Config, not the URL. The Graph base URL is shared; the tenant ID only parameterizes the token-endpoint URL. Keeping it in extra_config_json (seeded as {"tenant_id":""}) lets the same connector serve many Entra tenants without code changes.
  • Privileged-role enumeration uses activated roles only. GET /directoryRoles returns roles that have at least one member; roles no one is assigned don’t appear. That’s the desired behavior here — an unassigned role isn’t a risk.
  • Client secrets expire. Microsoft caps client-secret lifetime at 24 months. Schedule a rotation in your calendar — when the secret expires, every run starts failing with 401 at token exchange, and the Entra portal will not warn you.
  • Non-commercial clouds. For US Government, China (21Vianet), or Germany clouds, override the base URL to the right sovereign endpoint before saving.

Troubleshooting

  • HTTP 401 on Test with invalid_client — the client secret is wrong (you copied the Secret ID instead of the Value) or has expired. Generate a new secret and update Draxis.
  • HTTP 401 on Test with AADSTS90002 — the tenant ID in Extra Config is wrong. Copy the Directory (tenant) ID from the app registration’s Overview page.
  • HTTP 403 with Insufficient privileges — a required Graph permission is missing or admin consent hasn’t been granted. Re-open API permissions and confirm all five are green-checked.
  • HTTP 403 on /auditLogs/signIns or /identity/conditionalAccess — tenant doesn’t have Entra ID P1 or higher. Expected — see Quirks above.
  • entra_stale_accounts_90d equals the full enabled-member countAuditLog.Read.All is not granted, so Graph strips signInActivity from every user record. The connector treats missing last-sign-in as stale (fail-safe); fix the permission and the number will stabilize on the next run.
  • rowsSkipped > 0 and rowsWritten = 0 — your tenant hasn’t imported any KRIs for this integration yet. Open the integration in Settings → Integrations, tick the KRIs under KRIs to import, and save.
  • Still stuck? Open a support ticket with the run ID (from Run history) and we’ll dig in.