Skip to content

Microsoft Entra ID

Microsoft Entra ID (Azure AD) is the identity / directory source. The integration pulls users, groups and group memberships from the Microsoft Graph API and powers the Users surface, which links each person to the assets they own and to any badge / HR enrichment data. It follows the shared integration lifecycle.

  • Router: app/api/v1/endpoints/entra.py, mounted at /api/v1/entra
  • Client: app/services/entra_client.py (EntraClient)
  • Config collection: entra_config (keyed by tenant_id)

Authentication

Entra reuses the Intune app registration — the same Azure app, same MSAL client-credentials flow (certificate or secret), same https://graph.microsoft.com/.default scope — it just targets the directory endpoints and needs different permissions consented.

Required Graph application permissions (admin consent on the app registration):

  • User.Read.All
  • Group.Read.All (or GroupMember.Read.All / Directory.Read.All)

If consent is missing, Graph returns 403 and the client raises a clear EntraAuthError naming the permission to grant. Env seeding (_seed_entra_config) reuses the Intune env vars (INTUNE_TENANT_ID, INTUNE_CLIENT_ID, INTUNE_CERT_THUMBPRINT + PEM) and seeds the default tenant with certificate auth. test-connection fetches one user to validate credentials and consent.

What the sync pulls

The background _run_sync pulls three things:

DataSource callWritten to
Usersget_users (/users with $select + paging)entra_users
Groupsget_groups (/groups)entra_groups
Group membershipsget_group_members per groupresolved into each user’s groups list
  • Users capture UPN, display name, mail, job title, department, office, mobile, onPremisesSamAccountName, and enabled state. Crucially, the sync parses proxyAddresses into an aliases list (the smtp:-prefixed SMTP addresses, lowercased) — these let badge/HR rows whose email is an alias rather than the UPN still link to the right person.
  • Groups capture display name, description, mail, and derived flags (is_m365 from groupTypes containing Unified, is_security from securityEnabled), plus visibility.
  • Memberships are resolved best-effort per group into a map of upn → [group displayName], stored on each user as groups. A failure on any single group is non-fatal.

Stale users and groups (whose ids are no longer live) are deleted.

The Users surface

Beyond the standard integration routes, this router exposes a Users surface that reconciles a person across the whole system:

  • GET /api/v1/entra/users — searchable, paginated user list.
  • GET /api/v1/entra/user/{upn} — one user, enriched with:
    • Linked assets (_linked_devices) — devices this person owns, matched by UPN against intune_devices.user_upn and by UPN / display name against inventory_items (user_email / user_name). Each device resolves to the id the asset-detail page accepts (preferring the Intune UUID).
    • CSV enrichment — badge / HR rows from csv_users, matched by email against the user’s UPN, primary mail and any SMTP alias, so alias-based records still attach. Convenience flags (e.g. badge_admin) are surfaced at the top level.

This is how a machine, a directory identity, and an HR/badge record for the same human collapse into a single Users view.

Routes

MethodPathPurpose
POST / GET / DELETE/api/v1/entra/configSave / get (redacted) / remove config + data
POST/api/v1/entra/test-connectionLive Graph credential + consent test
POST/api/v1/entra/syncStart a background sync
GET/api/v1/entra/sync-statusPoll sync status
GET/api/v1/entra/statsUser / group / enabled-user counts
GET/api/v1/entra/usersList users (search, enabled-only)
GET/api/v1/entra/user/{upn}One user + linked devices + CSV enrichment

See the overview for the lifecycle these routes share, and the Users guide for the screen this powers.