Skip to content

Add WithRequestOverMtls() API and E2E test: attested bearer via IMDSv2 mTLS flow - #6086

Merged
Gladwin Johnson (gladjohn) merged 10 commits into
mainfrom
copilot/add-e2e-test-bearer-token-fallback
Aug 7, 2026
Merged

Add WithRequestOverMtls() API and E2E test: attested bearer via IMDSv2 mTLS flow#6086
Gladwin Johnson (gladjohn) merged 10 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: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
@gladjohn Gladwin Johnson (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
Gladwin Johnson (gladjohn) marked this pull request as ready for review June 23, 2026 18:30
@gladjohn
Gladwin Johnson (gladjohn) requested a review from a team as a code owner June 23, 2026 18:30
Copilot AI lite review requested due to automatic review settings June 23, 2026 18:30
@gladjohn
Gladwin Johnson (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 thread tests/Microsoft.Identity.Test.E2e/ManagedIdentityImdsV2Tests.cs
@gladjohn

Copy link
Copy Markdown
Contributor

We will need a new Bearer API to support this e2e

Comment thread tests/Microsoft.Identity.Test.E2e/ManagedIdentityImdsV2Tests.cs Outdated
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
@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.

Copilot AI review requested due to automatic review settings July 9, 2026 15:40

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

Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.

@gladjohn Gladwin Johnson (gladjohn) changed the title Add WithMtlsBearerToken() API and E2E test: Get attested ****** via IMDSv2 mTLS flow Add WithMtlsBearerToken() API and E2E test: Get attested Bearer via IMDSv2 mTLS flow Jul 10, 2026
…ards

- Rename public API WithMtlsBearerToken -> WithBearerOverMtls.
- Rename internal flag IsMtlsBearerRequested -> PreferMsiV2 (per bgavrilMS review on AcquireTokenCommonParameters).
- Add ArgumentNullException guard to WithBearerOverMtls (parity with WithMtlsProofOfPossession).
- Fail fast when WithMtlsProofOfPossession and WithBearerOverMtls are combined (mutually exclusive).
- Clarify the mTLS Bearer comment in ManagedIdentityAuthRequest.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e65d13d3-9f55-4d28-8224-6b08ff1bd85b
Resolve conflicts: drop removed MI ClientClaims propagation (main removed it); keep PreferMsiV2 routing; Unshipped.txt keeps only WithBearerOverMtls (the rest shipped on main).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e65d13d3-9f55-4d28-8224-6b08ff1bd85b
Copilot AI review requested due to automatic review settings August 4, 2026 23:08
@gladjohn
Gladwin Johnson (gladjohn) marked this pull request as ready for review August 4, 2026 23:09
@gladjohn Gladwin Johnson (gladjohn) changed the title Add WithMtlsBearerToken() API and E2E test: Get attested Bearer via IMDSv2 mTLS flow Add WithBearerOverMtls() API and E2E test: attested bearer via IMDSv2 mTLS flow Aug 4, 2026
@gladjohn

Copy link
Copy Markdown
Contributor

Updated the branch to current main and addressed the review feedback in the two latest commits:

  • Naming (Bogdan Gavril (@bgavrilMS)): renamed the internal flag IsMtlsBearerRequestedPreferMsiV2 (your suggestion) on AcquireTokenCommonParameters and throughout the plumbing. The public API is now WithBearerOverMtls() (renamed from WithMtlsBearerToken()).
  • Mutual exclusivity: WithMtlsProofOfPossession() and WithBearerOverMtls() now fail fast if both are set on the same request.
  • Null guard: added an ArgumentNullException guard to WithBearerOverMtls() (parity with WithMtlsProofOfPossession()).
  • Comment: clarified the "mTLS Bearer" comment in ManagedIdentityAuthRequest.
  • Merge with main: dropped the now-removed managed-identity ClientClaims propagation and kept the PreferMsiV2 routing; PublicAPI.Unshipped.txt now lists only WithBearerOverMtls (the rest shipped on main).

MI-focused unit tests pass locally (530 passed / 0 failed / 1 skipped, net8.0) and the E2E project compiles.

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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityPopExtensions.cs:90

  • The PR description and example code refer to a new public API named WithMtlsBearerToken(), but the implementation and PublicAPI entries introduce WithBearerOverMtls(). Please align the final public API name with the PR contract now (either rename the method/tests/PublicAPI to WithMtlsBearerToken, or update the PR description/example accordingly) to avoid confusion and potential follow-up breaking changes.
        public static AcquireTokenForManagedIdentityParameterBuilder WithBearerOverMtls(

src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityClient.cs:54

  • PreferMsiV2 is now routed through the same boolean path as IsMtlsPopRequested (by OR-ing them). This means that if the VM only supports IMDSv1, the code will throw MsalError.MtlsPopTokenNotSupportedinImdsV1 / MsalErrorMessage.MtlsPopTokenNotSupportedinImdsV1, which specifically mentions “mTLS Proof of Possession … private preview” and will be misleading for .WithBearerOverMtls() callers. Consider using a distinct error code/message for the mTLS-bearer-over-IMDSv2 requirement (or a more general mTLS-over-managed-identity not supported message) when PreferMsiV2 is the reason IMDSv2 is required.
            AbstractManagedIdentity msi = await GetOrSelectManagedIdentitySourceAsync(requestContext, parameters.IsMtlsPopRequested || parameters.PreferMsiV2, cancellationToken).ConfigureAwait(false);

Per naming discussion with Bogdan, rename the public managed identity API
WithBearerOverMtls() to WithRequestOverMtls(). Updates the method, the two
mutual-exclusivity error messages, the IMDSv1-fallback log hint, the XML
<see cref>, all 6 PublicAPI.Unshipped.txt files, and the E2E test
(call, method name, DataRow display names, comments).

The internal PreferMsiV2 flag and the confidential-client SendCertificateOverMtls
'bearer over mTLS' concept names are intentionally left unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e65d13d3-9f55-4d28-8224-6b08ff1bd85b
Copilot AI review requested due to automatic review settings August 7, 2026 14:18
@gladjohn Gladwin Johnson (gladjohn) changed the title Add WithBearerOverMtls() API and E2E test: attested bearer via IMDSv2 mTLS flow Add WithRequestOverMtls() API and E2E test: attested bearer via IMDSv2 mTLS flow Aug 7, 2026
@gladjohn

Copy link
Copy Markdown
Contributor

Renamed the public API WithBearerOverMtls()WithRequestOverMtls() per the naming discussion with Bogdan Gavril (@bgavrilMS) (WithRequestOverMtls chosen over WithMtlsTransport).

Scope of the rename (commit fb8a87b81):

  • Public method + both mutual-exclusivity error messages
  • IMDSv1-fallback log hint and the XML <see cref>
  • All 6 PublicAPI.Unshipped.txt files
  • E2E test: call, method name, DataRow display names, comments

Left intentionally unchanged: the internal PreferMsiV2 flag, and the confidential-client SendCertificateOverMtls "bearer over mTLS" concept names (a separate feature).

Product + E2E test projects build clean (0 warn / 0 err).

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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityPopExtensions.cs:91

  • The PR description/sample code refers to a new public API named WithMtlsBearerToken(), but the implementation and PublicAPI entries add WithRequestOverMtls(). Please align the PR description (and any intended naming) with the actual shipped API to avoid confusion for reviewers and consumers.
        /// <summary>
        /// Uses the IMDSv2 attested flow (Credential Guard–issued certificate over mTLS) to acquire
        /// a standard bearer token. The mTLS certificate authenticates the connection to the ESTS
        /// token endpoint, but the returned token carries <c>token_type=bearer</c> and has no
        /// binding certificate in the <see cref="AuthenticationResult"/>.
        /// Requires Windows Credential Guard (VBS) to be enabled on the host.
        /// When attestation is required, call <c>.WithAttestationSupport()</c> (from the
        /// <c>Microsoft.Identity.Client.KeyAttestation</c> package) after this method.
        /// </summary>
        /// <param name="builder">The AcquireTokenForManagedIdentityParameterBuilder instance.</param>
        /// <returns>The builder to chain .With methods.</returns>
        public static AcquireTokenForManagedIdentityParameterBuilder WithRequestOverMtls(
            this AcquireTokenForManagedIdentityParameterBuilder builder)

@gladjohn
Gladwin Johnson (gladjohn) merged commit 2686abc into main Aug 7, 2026
17 checks passed
@gladjohn
Gladwin Johnson (gladjohn) deleted the copilot/add-e2e-test-bearer-token-fallback branch August 7, 2026 15:03
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.

5 participants