Skip to content

Entra ID single sign-on

DTM supports Microsoft Entra ID single sign-on over OIDC. With it configured, users click Sign in with Microsoft in the web UI and authenticate against your tenant. DTM validates the sign-in and derives the user’s role from an app role you assign in Entra.

The sign-in uses the authorization-code flow with PKCE and validates the identity token against your tenant’s published signing keys. The API example below sends an admin bearer token as $DTM_TOKEN; the API overview shows how to capture one.

DTM reads the roles claim from the identity token:

  • Users assigned the Admin app role get the admin role in DTM.
  • Everyone else who can sign in gets read-only. DTM does not limit which accounts in your tenant can sign in, so unless you turn on Assignment required (step 8 below), that is every member and guest in the tenant.

Manage fine-grained editor access with local accounts, or assign the Admin app role to those who need full control.

If you set an SSO user’s role manually (over the API or UI), later sign-ins keep that manual role with one exception: when Entra proposes a lower role, the downgrade is honored and the manual role clears, so revoking someone’s Entra app role reliably demotes them. An Entra-proposed higher role never silently escalates a manually set one.

Local accounts and Entra identities are separate, disjoint namespaces. Enabling SSO changes nothing about existing local accounts, in either direction:

  • Mixed sign-in is the model. With SSO configured, the sign-in page offers both the Microsoft button and the local username/password form. Local accounts keep working, and there is deliberately no switch to turn local sign-in off: an Entra outage or misconfiguration must never lock every admin out.
  • An Entra user is its own account. The first Microsoft sign-in creates a DTM account keyed to the Entra user’s immutable subject, named with an entra: prefix (for example entra:[email protected]) and holding no local password. It shows up in the user list like any other account and can be disabled or deleted there; a disabled Entra user is refused at the Microsoft sign-in too.
  • Same name, no collision. A local alice and an Entra entra:[email protected] are unrelated accounts: neither inherits the other’s role or sessions, and the local password only ever signs in to the local account. Local usernames cannot contain :, so a local account can never occupy the entra: namespace. And because the Entra account is keyed to the immutable subject rather than the name, a UPN your tenant later reassigns to a different person maps to a new DTM account instead of inheriting the old one.
  • Order does not matter. Creating a local account that happens to match an Entra UPN, before or after enabling SSO, is allowed and has no effect on the Entra identity.
  • Turning SSO off later. Entra-created accounts hold no password, so they can no longer sign in; the accounts and their audit history remain, and the same people map back to the same accounts if you re-enable SSO. Local accounts are unaffected throughout.
  • Sessions. Enabling or disabling SSO does not revoke tokens that are already issued; they run out their 24-hour lifetime. Disabling or deleting any account, local or Entra, cuts its tokens off immediately, because every request re-checks the account.
  1. In the Azure portal, go to Microsoft Entra ID → App registrations → New registration.
  2. Give it a name (for example Hayami DTM).
  3. Add a redirect URI of type Web pointing at DTM’s callback endpoint, for example https://dtm.example.com/api/v1/auth/entra/callback.
  4. Note the Application (client) ID and Directory (tenant) ID.
  5. Under Certificates & secrets, create a client secret and copy its value.
  6. Under App roles, create an app role whose Value is exactly Admin (allowed member type: Users/Groups). DTM reads the role’s value from the sign-in token, not its display name, and the match is case-sensitive: a value of admin or DTM.Admin gives read-only access. The display name can be anything.
  7. Under Enterprise applications → your app → Users and groups, assign the Admin app role to the users or groups who should administer DTM.
  8. Under Enterprise applications → your app → Properties, set Assignment required? to Yes, so only the users and groups assigned to the app can sign in. Read-only users then need an assignment too: create a second app role for them (for example with the value Reader) and assign it. DTM gives read-only access to any role value other than Admin.

Provide the tenant ID, client ID, client secret, and redirect URI to DTM. You can do this from the Settings area of the UI, with the dtm_sso_config Terraform resource (the client secret is a write-only argument that never enters state; see users and SSO as code), or over the API (admin role):

Terminal window
curl -sk -X PUT https://dtm.internal:8443/api/v1/auth/entra/config \
-H "Authorization: Bearer $DTM_TOKEN" \
-H "X-DTM-CSRF: 1" \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "<tenant-id>",
"client_id": "<client-id>",
"client_secret": "<client-secret>",
"redirect_uri": "https://dtm.example.com/api/v1/auth/entra/callback"
}'

The stored client secret is redacted when you read the config back (GET /api/v1/auth/entra/config).

tenant_id must be your directory (tenant) ID, a GUID. The multi-tenant endpoints common, organizations and consumers are refused with 400, and so is the former allow_meta_tenant: true switch (from v1.0.4: they would let the administrator of any tenant that consents to the app make themselves a DTM admin). A node whose stored configuration still names one of them starts with Entra sign-in off and logs a warning naming the fix; local sign-in is unaffected. Set tenant_id to the GUID to turn it back on. From v1.0.5 the tenant ID is stored in lower case, so a GUID typed in upper case signs in normally, from the console, the API or Terraform.

From v1.0.5 the Entra configuration is cluster-wide. One save, through any node, from the Settings page, the API or Terraform, configures every node:

  • Every node applies it. The node that serves the save signs it and replicates it. Each node verifies it, stores the client secret encrypted at rest, and switches to it. The last save wins.
  • The first save after upgrading replaces each node’s own. A configuration a node held before v1.0.5 stays on that node: DTM never publishes one by itself. Once every node runs v1.0.5, the Settings page shows when the nodes’ configurations differ, and its button reads Apply to the whole cluster. That save replaces every node’s configuration, including any per-node redirect URI.
  • Before that first save, a save can stay on one node. It applies only to the node that serves it while any node runs an earlier version, while a member is down but has not been removed from the cluster, or on a node that has never reached its cluster (for example a replacement whose seeds have not answered yet). The API response says "replicated": false in each of these cases. After the first cluster-wide save, every save is cluster-wide, and a node still on an earlier version keeps its own configuration until it is upgraded.
  • Replacement nodes receive it. A node that joins later, such as the replacement in a blue/green swap, receives the cluster-wide configuration by replication.
  • Sign-in still completes on one node. On a cluster of more than one node, a Microsoft sign-in completes only on the node the redirect URI names, or behind a load balancer that keeps a user on one node between the start of the sign-in and the callback. Point users at that node’s address. If you gave each node its own redirect URI so that every node could complete sign-in, the first cluster-wide save ends that.
  • Check that SSO is active on the node (admin role):

    Terminal window
    curl -sk https://dtm.internal:8443/api/v1/auth/entra/config \
    -H "Authorization: Bearer $DTM_TOKEN"
    # {..., "configured": true, "active": true, "scope": "cluster", ...}

    configured means the tenant, client and secret are stored; active means this node actually offers and completes Entra sign-in. When a stored configuration is not active (a refused tenant, or a missing redirect_uri), active is false and inactive_reason says why, and the console’s Entra ID settings page shows Inactive with that reason. The public GET /api/v1/auth/entra/status reports configured only.

  • Check that every node holds the same configuration (from v1.0.5). In the same response, scope is cluster for the cluster-wide configuration and local for a node’s own, and version and updated_at identify the cluster-wide save. cluster.distinct_configs counts the different configurations the nodes hold: more than one means the nodes differ, or a node has not applied the latest save yet. A node on an earlier version reports no configuration and is not counted, so during an upgrade 1 does not prove every node matches. cluster.nodes lists each node with a fingerprint of its tenant, client and redirect URI, never the secret.

  • Open the UI sign-in page: a Sign in with Microsoft button appears. Sign in as a user with the Admin app role and confirm you have admin access; sign in as a user without it and confirm read-only access.