Skip to content

AuthenticationRequiredError drops the claims field from MSAL's InteractionRequiredAuthError, blocking OBO middle-tier claims propagation #38707

@localden

Description

@localden
  • Package Name: @azure/identity
  • Package Version: 4.13.1 (latest stable; same code on main at 4.14.0-beta.4)
  • Operating system: any
  • nodejs version: any
  • Is the bug related to documentation? no

When OnBehalfOfCredential.getToken() fails because Entra ID applies a Conditional Access policy at the OBO /token exchange, MSAL throws InteractionRequiredAuthError with a populated .claims field (the JSON the upstream client must send on its next /authorize to satisfy the policy). @azure/identity catches that error in msalClient.ts and runs it through handleMsalError, which wraps it in a fresh AuthenticationRequiredError({ scopes, getTokenOptions, message: error.message }). The .claims field is dropped and there is no cause chain, so the caller has no way to recover the directive.

This blocks the documented OBO Conditional Access pattern (docs): the middle-tier service is supposed to surface claims back to the calling client so the client can re-run interactive auth with ?claims=.... Without .claims on the thrown error, a middle-tier built on OnBehalfOfCredential can only show a generic "authentication required" failure.

To Reproduce

import { OnBehalfOfCredential } from "@azure/identity";

const cred = new OnBehalfOfCredential({
  tenantId, clientId, clientSecret,
  userAssertionToken: incomingAccessToken,
});

try {
  await cred.getToken(["https://graph.microsoft.com/.default"], { enableCae: true });
} catch (e) {
  // Tenant has "Require MFA for Office 365" CA policy.
  // MSAL threw InteractionRequiredAuthError with
  //   .claims = '{"access_token":{"capolids":{"essential":true,"values":["..."]}}}'
  // but e is AuthenticationRequiredError with only { scopes, getTokenOptions, message }.
  console.log((e as any).claims);  // undefined
  console.log((e as any).cause);   // undefined
}

Expected behavior

AuthenticationRequiredError should carry claims (or chain the original MSAL error via cause) so a middle-tier service can propagate the directive to its caller.

Relevant code

  • msalClient.ts getTokenOnBehalfOf catch: catch (err: any) { throw handleMsalError(scopes, err, options); }
  • utils.ts handleMsalError: InteractionRequiredAuthError has .name === "InteractionRequiredAuthError", which matches none of the checked names, so it falls through to return new AuthenticationRequiredError({ scopes, getTokenOptions, message: error.message }); claims and cause are not passed
  • errors.ts AuthenticationRequiredError: options interface is { scopes, getTokenOptions?, message?, cause? }; cause is already supported but no claims field exists

Proposed fix

Either of these would work:

  1. Add claims?: string to AuthenticationRequiredErrorOptions and AuthenticationRequiredError, and have handleMsalError pass claims: (error as InteractionRequiredAuthError).claims when present.
  2. Pass cause: error to the AuthenticationRequiredError constructor (the class already accepts options.cause, handleMsalError just doesn't supply it). Callers can then read (e.cause as InteractionRequiredAuthError).claims.

Additional context

The SDK already supports the resource-server side of CAE (#31501 in bearerTokenAuthenticationPolicy, and getToken({ claims, enableCae }) forwarding in msalClient.ts). This issue is specifically the authorization-server side: surfacing claims when the credential's own /token call fails. Workaround today is to bypass @azure/identity and use @azure/msal-node ConfidentialClientApplication.acquireTokenOnBehalfOf directly, which throws the InteractionRequiredAuthError raw, but that loses the IdentityClient retry policy.

Metadata

Metadata

Assignees

Labels

Azure.IdentityClientThis issue points to a problem in the data-plane of the library.customer-reportedIssues that are reported by GitHub users external to the Azure organization.needs-team-attentionWorkflow: This issue needs attention from Azure service team or SDK teamquestionThe issue doesn't require a change to the product in order to be resolved. Most issues start as that

Type

No type
No fields configured for issues without a type.

Projects

Status
Untriaged

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions