Skip to content

Add WithMtlsBearerToken() API and E2E test: Get attested ****** via IMDSv2 mTLS flow#6086

Draft
Copilot wants to merge 6 commits into
mainfrom
copilot/add-e2e-test-bearer-token-fallback
Draft

Add WithMtlsBearerToken() API and E2E test: Get attested ****** via IMDSv2 mTLS flow#6086
Copilot wants to merge 6 commits into
mainfrom
copilot/add-e2e-test-bearer-token-fallback

Conversation

Copilot AI commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Adds a new public API WithMtlsBearerToken() that uses the full IMDSv2 attested mTLS flow (Credential Guard–issued certificate, mTLS connection to ESTS) but requests token_type=bearer, returning a standard bearer token with no binding certificate. Also adds the corresponding E2E test.

Changes proposed in this request

  • Added WithMtlsBearerToken() extension method to ManagedIdentityPopExtensions.cs (Windows-only, mirrors WithMtlsProofOfPossession() but sends token_type=bearer to ESTS instead of token_type=mtls_pop)
  • Plumbed IsMtlsBearerRequested flag through AcquireTokenCommonParameters, AcquireTokenForManagedIdentityParameters, AuthenticationRequestParameters, AcquireTokenForManagedIdentityParameterBuilder, ManagedIdentityAuthRequest, and AbstractManagedIdentity
  • Updated routing in ManagedIdentityClient to route IsMtlsBearerRequested to IMDSv2 (same as IsMtlsPopRequested)
  • Extended the KeyGuard validation in ImdsV2ManagedIdentitySource.CreateRequestAsync to cover both PoP and ****** under a unified credential_guard_not_available error code
  • Added a separate cache partition for mTLS-bearer tokens (mtls_bearer key component) to keep them distinct from PoP tokens and plain IMDSv1 bearer tokens
  • Updated PublicAPI.Unshipped.txt for all target frameworks
  • Replaced the original AcquireToken_OnImdsV2_WithAttestation_NoMtlsPoP_ReturnsBearer E2E test with AcquireToken_OnImdsV2_WithMtlsBearerToken_ReturnsBearer, which calls .WithMtlsBearerToken().WithAttestationSupport() and asserts TokenType == "Bearer", BindingCertificate == null, absence of the cnf claim in the JWT payload, and TokenSource == IdentityProvider
  • Updated unit test error code assertion to credential_guard_not_available
var result = await mi.AcquireTokenForManagedIdentity(GraphResource)
    .WithMtlsBearerToken()      // uses IMDSv2 attested flow, token_type=bearer
    .WithAttestationSupport()   // adds Credential Guard attestation JWT
    .ExecuteAsync()
    .ConfigureAwait(false);

Assert.AreEqual("Bearer", result.TokenType);
Assert.IsNull(result.BindingCertificate);
var jwt = new JwtSecurityToken(result.AccessToken);
Assert.IsFalse(jwt.Payload.ContainsKey("cnf"));
Assert.AreEqual(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource);

Testing
Updated E2E test uses .WithMtlsBearerToken() and runs on the MSALMSIV2 pool (Credential Guard / VBS enabled). Gracefully marks inconclusive if Credential Guard is unavailable. Unit test for the KeyGuard-not-available error path updated to match the new unified error code.

Performance impact
None.

Documentation

  • All relevant documentation is updated.

Copilot AI requested review from Copilot and removed request for Copilot June 23, 2026 17:34
Copilot AI requested review from Copilot and removed request for Copilot June 23, 2026 17:38
Copilot AI changed the title [WIP] Add E2E test for Bearer token fallback on IMDSv2 Add E2E test: ****** when WithMtlsProofOfPossession() is omitted on IMDSv2 host Jun 23, 2026
Copilot AI requested a review from gladjohn June 23, 2026 17:39
@gladjohn gladjohn changed the title Add E2E test: ****** when WithMtlsProofOfPossession() is omitted on IMDSv2 host Add E2E test: Get Bearer when WithMtlsProofOfPossession() is omitted on IMDSv2 host - Still need CVM/TVM + KeyGuard Jun 23, 2026
@gladjohn gladjohn marked this pull request as ready for review June 23, 2026 18:30
@gladjohn gladjohn requested a review from a team as a code owner June 23, 2026 18:30
Copilot AI review requested due to automatic review settings June 23, 2026 18:30
@gladjohn gladjohn marked this pull request as draft June 23, 2026 18:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an end-to-end regression test in the Managed Identity IMDSv2 E2E suite to validate that when .WithMtlsProofOfPossession() is omitted (even if .WithAttestationSupport() is present), MSAL returns a standard Bearer token (no binding certificate) on an IMDSv2-capable host.

Changes:

  • Added a new [DataRow]-driven E2E test covering SAMI and UAMI (clientId) scenarios for the “no mTLS PoP requested” path.
  • Asserts Bearer token shape (TokenType == "Bearer", BindingCertificate == null) and that the token was acquired from the identity provider.

Comment on lines +137 to +140
Assert.IsFalse(string.IsNullOrEmpty(result.AccessToken), "AccessToken should not be empty.");
Assert.AreEqual("Bearer", result.TokenType, "Token type should be 'Bearer' when mTLS PoP is not requested.");
Assert.IsNull(result.BindingCertificate, "BindingCertificate should be null for a Bearer token.");

@gladjohn

Copy link
Copy Markdown
Contributor

We will need a new Bearer API to support this e2e

[TestMethod]
[DataRow(null /*SAMI*/, null, DisplayName = "AcquireToken_OnImdsV2_WithAttestation_NoMtlsPoP_ReturnsBearer-SAMI")]
[DataRow(UamiClientId, "clientid", DisplayName = "AcquireToken_OnImdsV2_WithAttestation_NoMtlsPoP_ReturnsBearer-UAMI-ClientId")]
public async Task AcquireToken_OnImdsV2_WithAttestation_NoMtlsPoP_ReturnsBearer(string id, string idType)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot This adds an E2E test covering the IMDSv1 path on an IMDSv2-capable host: when .WithMtlsProofOfPossession() is not called, MSAL routes the request through IMDSv1 regardless of .WithAttestationSupport(), returning a bearer token with no binding certificate.

But, to get a Bearer token .WithAttestationSupport(), we may need a new let's say name not final - WithMtlsBearer() - think of a good name. If we added this, then we should use the Attested Flow in IMDSv2-capable host and get a bearer token. We just need to set the token type to bearer when we call ESTS finally with the cert over mTLS. Can you add the new api and update this test to return a bearer token using attestation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added WithMtlsBearerToken() in commit 9c06b52.

The new API follows the same IMDSv2 attested flow as WithMtlsProofOfPossession() — it obtains a Credential Guard–issued certificate, connects to ESTS over mTLS — but sends token_type=bearer instead of token_type=mtls_pop, returning a plain bearer token with no binding certificate.

Key changes:

  • WithMtlsBearerToken() extension added to ManagedIdentityPopExtensions.cs (Windows-only, same as the PoP variant)
  • IsMtlsBearerRequested flag plumbed from the builder through parameters, routing, and ImdsV2ManagedIdentitySource.CreateRequestAsync
  • Routing in ManagedIdentityClient treats IsMtlsBearerRequested the same as IsMtlsPopRequested for IMDSv2 source selection
  • The KeyGuard check in ImdsV2ManagedIdentitySource now covers both PoP and ****** (unified error code credential_guard_not_available)
  • mTLS-bearer tokens get their own cache partition (mtls_bearer key component) to keep them separate from both PoP tokens and plain IMDSv1 bearer tokens
  • E2E test updated to call .WithMtlsBearerToken().WithAttestationSupport() and asserts TokenType == "Bearer", BindingCertificate == null, and absence of the cnf claim in the JWT payload

Copilot AI requested review from Copilot and removed request for Copilot June 25, 2026 19:18
Copilot AI changed the title Add E2E test: Get Bearer when WithMtlsProofOfPossession() is omitted on IMDSv2 host - Still need CVM/TVM + KeyGuard Add WithMtlsBearerToken() API and E2E test: Get attested ****** via IMDSv2 mTLS flow Jun 25, 2026
Copilot AI requested a review from gladjohn June 25, 2026 19:19
@gladjohn

Copy link
Copy Markdown
Contributor

@copilot resolve conflicts

Copilot AI requested review from Copilot and removed request for Copilot June 26, 2026 13:44

Copilot AI commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

@copilot resolve conflicts

Conflicts resolved in commit d5a3f7c (merge commit). The main branch had introduced MtlsPopMinStrength/PoPOptions and refactored ImdsV2ManagedIdentitySource to delegate the token leg via AcquireMtlsBindingAsync(). Key resolutions:

  • All six PublicAPI.Unshipped.txt files now include both our WithMtlsBearerToken entry and main's new PoPOptions/MinStrengthNotMet/WithMtlsProofOfPossession(PoPOptions) entries.
  • Both IsMtlsBearerRequested and MtlsPopMinStrength are kept in all parameter/request types.
  • AcquireMtlsBindingForDelegationAsync now sets _isMtlsBearerRequested from parameters (instead of hardcoding _isMtlsPopRequested = true), and ManagedIdentityAuthRequest routes both IsMtlsPopRequested and IsMtlsBearerRequested into the delegation path with appropriate auth scheme handling for each.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants