{"record":{"id":"985709c22be39040","repo":"microsoft/aspire","slug":"unable-to-determine-the-azure-identity-to-provision-as-the","errorCode":null,"errorMessage":"Unable to determine the Azure identity to provision as: the access token returned by the credential does not contain a valid 'oid' (object id) claim.","messagePattern":"Unable to determine the Azure identity to provision as: the access token returned by the credential does not contain a valid 'oid' \\(object id\\) claim\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting.Azure/Provisioning/Internal/DefaultAzurePrincipalProvider.cs","lineNumber":79,"sourceCode":"            var bytes = Convert.FromBase64String(convertedToken);\n\n            // Read claims from the root object only. JWT claims are top-level by definition, but a\n            // claim's *value* can itself be an object or array — Entra emits `_claim_sources` that\n            // way for the groups-overage case, and RFC 8693 delegation tokens nest identity claims\n            // under `act`. A streaming reader that walks every token would treat a nested \"oid\" or\n            // \"idtyp\" as if it were a real claim, and last-write-wins would silently swap the\n            // principal these values describe. That matters here because they become the\n            // principalId/principalType of an ARM role assignment, so picking up the wrong one\n            // would grant access to the wrong identity. Microsoft also documents that new claims\n            // may be added without notice, so scope the lookup structurally rather than relying on\n            // today's payloads happening to be flat.\n            using var document = JsonDocument.Parse(bytes);\n            var root = document.RootElement;\n\n            var oid = GetRootString(root, \"oid\");\n            if (!Guid.TryParse(oid, out var principalId))\n            {\n                throw new InvalidOperationException(\n                    \"Unable to determine the Azure identity to provision as: the access token returned by \" +\n                    \"the credential does not contain a valid 'oid' (object id) claim.\");\n            }\n\n            // Default to \"User\" so older tokens — and any flow that omits `idtyp` — keep the\n            // historical behavior of a hardcoded \"User\" principalType instead of regressing to an\n            // empty value. `idtyp` is an optional claim that Entra only emits for app-only tokens\n            // unless the resource opts in via `include_user_token`, so absence is not evidence of\n            // a user identity; it just means we can't tell and fall back to the previous default.\n            // The comparison is case-insensitive for resilience against future producers that emit\n            // different casing than the lower-case values Entra documents.\n            var isAppOnly = string.Equals(GetRootString(root, \"idtyp\"), IdTypApp, StringComparison.OrdinalIgnoreCase);\n\n            var principalType = isAppOnly\n                ? PrincipalTypeServicePrincipal\n                : PrincipalTypeUser;\n\n            return new AzurePrincipal(principalId, ResolvePrincipalName(root, principalId, isAppOnly), principalType);","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting.Azure/Provisioning/Internal/DefaultAzurePrincipalProvider.cs#L61-L97","documentation":"After decoding the JWT payload, GetPrincipalAsync extracts the 'oid' (object id) claim that identifies the Azure identity to provision as. If the claim is missing or not a parseable Guid, the provider throws because ARM provisioning and role assignment need a concrete principal id. This means the token is a JWT but not an ARM-appropriate access token bearing an object id.","triggerScenarios":"GetPrincipalAsync decodes a token whose payload lacks 'oid' or has a non-Guid value — e.g. using a credential scoped to a non-ARM audience (Graph-only or opaque tokens), a token minted by a non-Entra ID STS, or a fabricated test token with an invalid oid.","commonSituations":"Developers hit this with custom credentials fetching tokens for the wrong resource/audience, tokens from non-Entra identity providers, tenant-mismatched service principals, or test doubles whose payload omits oid.","solutions":["Ensure the credential requests tokens for the Azure ARM audience (https://management.azure.com/) so Entra ID includes the oid claim.","Verify the identity is a real Entra ID object (user, service principal, or managed identity); check with `az ad signed-in-user show` that an id exists.","Set AZURE_TENANT_ID/AZURE_CLIENT_ID/AZURE_CLIENT_SECRET (or use DefaultAzureCredential) so a valid Entra token is issued.","In tests, emit a payload with a valid Guid 'oid' claim, e.g. {\"oid\":\"00000000-0000-0000-0000-000000000000\"}."],"exampleFix":"// before\nvar token = await cred.GetTokenAsync(new TokenRequestContext(new[] { \"https://graph.microsoft.com/.default\" }), ct); // wrong audience\n// after\nvar token = await cred.GetTokenAsync(new TokenRequestContext(new[] { \"https://management.azure.com/.default\" }), ct); // ARM audience includes oid","handlingStrategy":"validation","validationCode":"var payload = DecodeJwtPayload(token); // base64url decode middle segment\nusing var doc = JsonDocument.Parse(payload);\nvar ok = doc.RootElement.TryGetProperty(\"oid\", out var oid)\n         && Guid.TryParse(oid.GetString(), out _);\nif (!ok) throw new InvalidOperationException(\"Token has no valid 'oid' claim; request a token for the ARM audience.\");","typeGuard":"bool HasValidOidClaim(string jwt)\n{\n    try\n    {\n        var payload = jwt.Split('.')[1].Replace('-', '+').Replace('_', '/').PadRight(4 * ((jwt.Split('.')[1].Length + 3) / 4), '=');\n        using var doc = JsonDocument.Parse(Convert.FromBase64String(payload));\n        return doc.RootElement.TryGetProperty(\"oid\", out var oid) && Guid.TryParse(oid.GetString(), out _);\n    }\n    catch { return false; }\n}","tryCatchPattern":"try\n{\n    var principal = await principalProvider.GetPrincipalAsync(ct);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"'oid'\") && ex.Message.Contains(\"claim\"))\n{\n    // request an ARM-audience token (https://management.azure.com/.default) with a valid Entra identity\n}","preventionTips":["Request tokens for the Azure ARM audience (https://management.azure.com/.default), not Graph-only scopes.","Confirm the identity exists in Entra ID with `az ad signed-in-user show`.","Use DefaultAzureCredential or properly configured service principal env vars.","In tests, include a valid Guid 'oid' claim in fabricated token payloads."],"tags":["azure","jwt","oid","identity","claims"],"backgroundTag":"jwt-token-expired","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}