Skip to content

Support Microsoft Entra v2 access tokens for Windows MDM enrollment #46388

@getvictor

Description

@getvictor

Goal

User story
As an IT admin enrolling Windows devices into Fleet via Microsoft Entra,
I want Fleet to validate v2 access tokens (where the aud claim is the application's client ID) in addition to v1 tokens,
so that on-premises MDM applications created in Entra after July 1, 2026 can successfully enroll devices into Fleet.

Video demo: https://www.youtube.com/watch?v=t3yuGh0kwP8
Docs: #46483

Context

From July 1, 2026, on-premises MDM applications created in Entra issue v2 access tokens, whose audience (aud) is the application's client ID (a GUID) instead of the app's identifier URI (the Fleet server URL). Fleet's audience check parsed aud as a URL and matched the host, so v2 tokens silently failed with "token audience is not authorized." Existing v1 apps are unaffected; the new validation is additive and backward compatible.

Implementation

Backend

  • New AppConfig field mdm.windows_entra_client_ids (flat list, parallel to windows_entra_tenant_ids). Premium + Windows-MDM-enabled + GUID-validated.
  • v2 audience check: token aud matched case-insensitively against configured client IDs. v1 URL-host check unchanged (additive).
  • GUID validation accepts upper- or lower-case; tid (tenant) and aud (client) compared case-insensitively, no normalization on write.
  • v2 tokens omit the v1-only unique_name claim and use preferred_username for UPN. upn now falls back to preferred_username, and unique_name is optional. (This was the bug that blocked the first real v2 enrollment.)
  • Migration 20260529120000_AddAppConfigMDMWindowsEntraClientIDs seeds the field to [].

Activities (correction to the original spec: these were added, for parity with tenant IDs)

  • added_microsoft_entra_client_id / deleted_microsoft_entra_client_id, detail {"client_id": "<guid>"}, emitted per added/removed ID.

Frontend / GitOps / API

  • "Entra application client IDs" section under Settings > Integrations > MDM > Microsoft Entra > Windows enrollment (add/remove modals). Read-only in GitOps mode (Add and Delete), matching tenant IDs.
  • GitOps apply + fleetctl generate-gitops support controls.windows_entra_client_ids.
  • GET/PATCH /api/_version_/fleet/config accept mdm.windows_entra_client_ids (array of GUIDs; 422 on invalid).

Findings

Switch an app to v2 for testing. The setting shown in the Entra manifest UI as accessTokenAcceptedVersion maps to Graph's api.requestedAccessTokenVersion (1 = v1, 2 = v2). Flip it with the Azure CLI rather than hand-editing the manifest:

OBJECT_ID=$(az ad app show --id <CLIENT_ID> --query id -o tsv)
az rest --method PATCH \
  --uri "https://graph.microsoft.com/v1.0/applications/${OBJECT_ID}" \
  --headers "Content-Type=application/json" \
  --body '{"api":{"requestedAccessTokenVersion":2}}'

Revert with 1. Verify: az ad app show --id <CLIENT_ID> --query "api.requestedAccessTokenVersion".

  • A v2 token with no matching client ID configured makes the device show "Device management could not be enabled" (0x801900c8). Fleet logs the unexpected aud and the configured client IDs so admins can copy in the right value.
  • Find the value at Entra ID > App registrations > [your MDM app] > Overview > Application (client) ID.

Test plan (manual QA)

Prerequisites

  • Fleet Premium with Windows MDM turned on (WSTEP certificate configured).
  • A Microsoft Entra tenant with an on-premises MDM application, a licensed Entra test user, and a Windows 10/11 test device (freshly wiped or new).
  • Global Admin access to Fleet.

Setup: force the Entra app to issue v2 tokens (on-prem MDM apps default to v1 until 2026-07-01)

  1. Set requestedAccessTokenVersion to 2 (see Findings for the az rest command, or edit the app manifest in the Entra portal).
  2. Copy the Application (client) ID from Entra ID > App registrations > [your MDM app] > Overview.

1. Positive: v2 token is accepted

  1. In Fleet, go to Settings > Integrations > MDM > Microsoft Entra > Windows enrollment. Ensure the tenant ID is added, then under Entra application client IDs add the client ID from setup.
  2. On the test device, enroll via Settings > Access work or school > Connect, signing in with the Entra test user.
  3. Expected: the device enrolls and appears in Fleet as a Windows host. (Optional: decode the enrollment token at jwt.ms and confirm aud is the client ID GUID, not a URL.)

2. Negative: v2 token rejected when the client ID is not configured

  1. Remove the client ID in Fleet (keep the tenant ID). Unenroll, then re-enroll the device.
  2. Expected: enrollment fails. The device shows "Device management could not be enabled" (0x801900c8). The Fleet server log (debug) shows "token audience is not authorized" with the unexpected aud and the configured client IDs.

3. Backward compatibility: v1 still enrolls

  1. Set requestedAccessTokenVersion back to 1. Ensure the tenant ID is configured; no client ID is required.
  2. Re-enroll the device. Expected: the device enrolls (no regression for existing v1 customers).

4. UI

  • Add a client ID with a valid GUID (upper or lower case): accepted. Add a non-GUID: inline validation error.
  • Remove a client ID. Verify the empty and populated list states render.
  • Adding or removing a client ID records an activity in the activity feed (added_microsoft_entra_client_id / deleted_microsoft_entra_client_id).
  • Enable GitOps mode: the Add and Delete controls are disabled with the GitOps tooltip (read-only), matching tenant IDs.

5. GitOps

  • fleetctl generate-gitops emits controls.windows_entra_client_ids with the configured GUIDs.
  • Edit the YAML (add or remove a client ID) and run fleetctl gitops; confirm Fleet reflects the change.

6. API

  • PATCH /api/latest/fleet/config with mdm.windows_entra_client_ids set to a list of GUIDs returns 200 and persists; GET returns them.
  • PATCH with a non-GUID entry returns 422 naming the field.

7. Permissions

  • Global Admin and GitOps roles can edit client IDs; Maintainer, Observer, and Observer+ cannot.

Confirmation

  1. Engineer: Added comment to user story confirming successful completion of test plan (include any special setup, test data, or configuration used during development/testing if applicable).
  2. QA: Added comment to user story confirming successful completion of test plan.
  3. QA: Determined whether this story needs Playwright automation.
    • Needs automation: No

Metadata

Metadata

Assignees

Labels

#g-power-to-pcPower to the PC working groupstoryA user story defining an entire feature

Type

No type
No fields configured for issues without a type.

Projects

Status

🐥 Ready for review

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions