Skip to content

feat: extend mTLS bearer transport (SendCertificateOverMtls) to OBO, refresh_token, and auth_code flows#6009

Open
Robbie-Microsoft wants to merge 23 commits into
mainfrom
rginsburg/mtls_bearer_user_flows
Open

feat: extend mTLS bearer transport (SendCertificateOverMtls) to OBO, refresh_token, and auth_code flows#6009
Robbie-Microsoft wants to merge 23 commits into
mainfrom
rginsburg/mtls_bearer_user_flows

Conversation

@Robbie-Microsoft
Copy link
Copy Markdown
Contributor

@Robbie-Microsoft Robbie-Microsoft commented May 14, 2026

Summary

When SendCertificateOverMtls=true, MSAL previously only applied mTLS bearer transport for AcquireTokenForClient. User flows (OBO, refresh_token, auth_code) continued to use the regular login.microsoftonline.com endpoint with a client_assertion JWT in the POST body.

This PR extends mTLS bearer transport to all three user flows so they behave consistently with client_credentials when mTLS transport is configured.

Root Cause

TryInitMtlsPopParametersAsync (which sets MtlsCertificate on the request parameters, triggering mTLS endpoint routing) was only called in ConfidentialClientExecutor.ExecuteAsync for AcquireTokenForClientParameters. The OBO, auth_code, and RT executor paths skipped it entirely.

Changes

Production

File Change
ConfidentialClientExecutor.cs Add TryInitMtlsPopParametersAsync to OBO and auth_code executor paths
ClientApplicationBaseExecutor.cs Add TryInitMtlsPopParametersAsync to the refresh_token (IByRefreshToken) and AcquireTokenSilent paths
RegionAndMtlsDiscoveryProvider.cs Attempt region discovery for mTLS user flows when WithAzureRegion is configured, so regional endpoints (e.g. eastus.mtlsauth.microsoft.com) are used
TokenCache.ITokenCacheInternal.cs Use OriginalAuthority for cache alias resolution so mTLS-transformed (mtlsauth.*) endpoints do not crash on second call
MtlsPopParametersInitializer.cs Case 2 (IClientSignedAssertionProvider.GetAssertionAsync) is not guarded by SendCertificateOverMtls — it is a separate opt-in where the delegate signals mTLS intent by returning a non-null TokenBindingCertificate. This is independent of Case 1 (SendCertificateOverMtls + cert-based credential).
CredentialMaterialResolver.cs Mode=Mtls only when IsMtlsPopRequested (explicit PoP); Mode=OAuth for all bearer transport cases so client_assertion is included in the body for every flow. Also auto-enables SendX5C=true when SendCertificateOverMtls=true — required for SNI-registered apps to validate the assertion JWT via the x5c chain.
AcquireTokenParameterBuilderExtensions.cs, AcquireTokenCommonParameters.cs, TokenClient.cs, AuthenticationRequestParameters.cs, PublicAPI.Unshipped.txt (×6) Restored WithCachePartitionKey, WithReservedScopes, and SendOfflineAccessScope lost during a prior merge conflict resolution (from PR #6014 / 022dcde32)

Tests

File Description
MtlsBearerUserFlowTests.cs (new) Unit tests: OBO global mTLS, RT global mTLS, regional mTLS routing (full OBO post-data assertions including grant_type and requested_token_use), regression for non-mTLS cert credential, regression for second-call cache crash
MtlsTransportUserFlowTests.cs (new) Integration tests: OboFlow/RefreshTokenFlow/AuthCodeFlow/ClientCredentials_WithSendCertificateOverMtls_BothMtlsConditionsMet, OboFlow_WithoutSendCertificateOverMtls_UsesRegularEndpointAsync. OboFlow/RefreshTokenFlow_WithSendCertificateOverMtls_AcquiresTokenAsync are [Ignore]'d pending lab config (see Testing section).

Docs

File Description
docs/mtls-bearer-transport.md (new) User-facing guide: opt-in API, IMsalMtlsHttpClientFactory implementation example, supported flows, how to verify, AAD allowlisting requirement

Design: Preview Behavior

For this preview drop, all flows send client_assertion in the POST body and present the cert at the TLS layer when SendCertificateOverMtls=true. This matches what ESTS supports today across all grant types.

Flow Endpoint client_assertion in body
AcquireTokenForClient (S2S) mtlsauth.microsoft.com Yes
OBO mtlsauth.microsoft.com Yes
refresh_token mtlsauth.microsoft.com Yes
auth_code mtlsauth.microsoft.com Yes

"Cert-only" (no client_assertion) is a future ESTS change, deferred post-preview.

Bug Fixes (found during review)

Bug 1 - Cache crash on second mTLS call:
TokenCache.ITokenCacheInternal.cs used requestParams.AuthorityInfo (a live property that returns the mTLS-transformed mtlsauth.* host after ResolveAuthorityAsync) for cache alias resolution. RegionAndMtlsDiscoveryProvider throws MtlsPopNotSupportedForEnvironment for non-login.* hosts. First call was safe (empty cache, early return); second call had cached entries and crashed. Fixed by using requestParams.AuthorityManager.OriginalAuthority.AuthorityInfo.

Bug 2 - AcquireTokenSilent not routing to mTLS endpoint:
ClientApplicationBaseExecutor.ExecuteAsync(AcquireTokenSilentParameters) never called TryInitMtlsPopParametersAsync, so silent refresh-token redemptions always hit login.microsoftonline.com even when SendCertificateOverMtls=true.

Bug 3 - SNI regression: missing x5c in client_assertion:
Changing CredentialMaterialResolver.cs to Mode=OAuth for all non-PoP requests caused CertificateAndClaimsClientCredential to start sending a client_assertion JWT where previously none was sent. For SNI-registered apps, AAD requires the x5c chain in the JWT header to validate the assertion. Without x5c, AADSTS700027 is returned. Fixed by auto-enabling SendX5C=true when SendCertificateOverMtls=true.

Bug 4 - TokenBindingCertificate unit tests broken by overly-broad guard:
An earlier review comment suggested guarding Case 2 in MtlsPopParametersInitializer with SendCertificateOverMtls == true. This broke BearerClientAssertion_WithPoPDelegate_Works and 3 related tests because Case 2 is a separate opt-in mechanism that must fire regardless of SendCertificateOverMtls. The guard was removed.

Testing

Unit Tests (net8.0): 2,063 passed, 0 failed

Integration Tests:

  • Sni_Over_Mtls_Gets_Bearer_Token_Successfully_TestAsync — passes (pre-existing test, fixed by Bug 3 fix above)
  • ClientCredentials_WithSendCertificateOverMtls_BothMtlsConditionsMet — passes (MSI-allowlisted app, 163ffef9)
  • OboFlow_WithSendCertificateOverMtls_BothMtlsConditionsMet — passes (recording factory)
  • RefreshTokenFlow_WithSendCertificateOverMtls_BothMtlsConditionsMet — passes (recording factory)
  • AuthCodeFlow_WithSendCertificateOverMtls_BothMtlsConditionsMetAsync — passes (fake auth code, recording factory)
  • OboFlow_WithoutSendCertificateOverMtls_UsesRegularEndpointAsync — passes
  • OboFlow_WithSendCertificateOverMtls_AcquiresTokenAsync[Ignore] pending lab config: AppWebApi (23c64cd8) mTLS not yet enabled in ID4SLAB1 (AADSTS700027). Remove [Ignore] once Bogdan/Qi enable mTLS client auth.
  • RefreshTokenFlow_WithSendCertificateOverMtls_AcquiresTokenAsync[Ignore] pending lab config: AppS2S mTLS endpoint not yet configured for this scenario (AADSTS392189). Remove [Ignore] once Bogdan/Qi enable mTLS client auth.

Existing MtlsPopTests suite: 69/69 pass.

…de flows

When SendCertificateOverMtls=true, MSAL previously only routed
AcquireTokenForClient to the mTLS endpoint (mtlsauth.microsoft.com) and
suppressed client_assertion from the POST body. User flows (OBO,
refresh_token, auth_code) fell through to the regular login endpoint with
a client_assertion JWT.

This change extends the feature to all three user flows by calling
TryInitMtlsPopParametersAsync in each executor path, mirroring the existing
AcquireTokenForClient behaviour.

Changes:
- ConfidentialClientExecutor: add TryInitMtlsPopParametersAsync to OBO and
  auth_code executor paths
- ClientApplicationBaseExecutor: add TryInitMtlsPopParametersAsync to the
  refresh_token (IByRefreshToken) executor path
- RegionAndMtlsDiscoveryProvider: attempt region discovery for mTLS-enabled
  user flows when the app has configured WithAzureRegion, so regional mTLS
  endpoints (e.g. eastus.mtlsauth.microsoft.com) are used for OBO/RT
- TokenCache: use OriginalAuthority for cache alias resolution so that
  mTLS-transformed (mtlsauth.*) endpoints do not propagate into cache lookups

Tests:
- MtlsBearerUserFlowTests.cs: 4 unit tests (OBO global/regional mTLS, RT
  global mTLS, regression for non-mTLS cert credential)
- MtlsTransportUserFlowTests.cs: updated integration tests asserting both mTLS
  transport conditions (mtlsauth endpoint + no client_assertion) for OBO and RT

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 14, 2026 17:28
@Robbie-Microsoft Robbie-Microsoft requested a review from a team as a code owner May 14, 2026 17:28
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Extends mTLS bearer transport behavior (SendCertificateOverMtls) beyond client_credentials to OBO, refresh_token, and auth_code flows, ensuring mTLS endpoint routing and suppressing client_assertion where appropriate.

Changes:

  • Initialize mTLS/PoP parameters for auth_code, OBO, and refresh_token executor paths.
  • Enable region discovery for mTLS-enabled user flows when AzureRegion is configured (regional mtlsauth endpoints).
  • Prevent mTLS-transformed authorities from leaking into token cache alias resolution by using OriginalAuthority.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tests/Microsoft.Identity.Test.Unit/PublicApiTests/MtlsBearerUserFlowTests.cs Adds unit coverage validating mtlsauth routing + client_assertion suppression for OBO/RT, plus a non-mTLS regression case
tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/MtlsTransportUserFlowTests.cs Adds/updates integration coverage for mTLS transport factory usage and asserts endpoint/body conditions for OBO/RT/client_credentials
src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs Uses OriginalAuthority for alias resolution to avoid mtlsauth host affecting cache lookups
src/client/Microsoft.Identity.Client/Instance/Discovery/RegionAndMtlsDiscoveryProvider.cs Attempts region discovery for mTLS user flows when AzureRegion is configured
src/client/Microsoft.Identity.Client/ApiConfig/Executors/ConfidentialClientExecutor.cs Calls TryInitMtlsPopParametersAsync for auth_code and OBO paths
src/client/Microsoft.Identity.Client/ApiConfig/Executors/ClientApplicationBaseExecutor.cs Calls TryInitMtlsPopParametersAsync for refresh_token path

Robbie-Microsoft and others added 2 commits May 14, 2026 14:16
CertHelper.GetOrCreateTestCert() returns a static cached instance.
Calling Dispose() in ClassCleanup poisons the cache, causing
MtlsPopTests.ClassInitialize to receive a disposed X509Certificate2
(m_safeCertContext is an invalid handle) when it runs alphabetically
after MtlsBearerUserFlowTests.

CertHelper owns the certificate lifetime; test classes must not
dispose certs obtained from it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Clear MSAL_FORCE_REGION in regional unit test for defensive isolation
- Clarify assertion messages to specify GetHttpClient(X509Certificate2) overload

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 14, 2026 19:18
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 6 out of 6 changed files in this pull request and generated 5 comments.

Comment thread tests/Microsoft.Identity.Test.Unit/PublicApiTests/MtlsBearerUserFlowTests.cs Outdated
- Fix 'requestwith' typo in XML doc
- Clarify that ExpectedPostData checks client_assertion_type (not the
  client_assertion value itself, which is a dynamically generated JWT)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread tests/Microsoft.Identity.Test.Unit/PublicApiTests/MtlsBearerUserFlowTests.cs Outdated
bgavrilMS

This comment was marked as resolved.

…Flow test, no Console.WriteLine

- MtlsTransportUserFlowTests: replace secret-based OBO/RT factory tests with
  cert+SendCertificateOverMtls=true (OboFlow_WithSendCertificateOverMtls_AcquiresTokenAsync,
  RefreshTokenFlow_WithSendCertificateOverMtls_AcquiresTokenAsync), making them true mTLS
  integration tests that assert on both the mTLS endpoint and factory invocation
- Remove SilentFlow_WithMtlsTransportFactory_UsesRefreshTokenOverMtlsAsync: it attached
  IMsalMtlsHttpClientFactory to a PCA (public client), which does not perform cert-based
  client authentication; the test did not exercise the feature being changed
- Remove _oboClientSecret, _keyVault, and secret-based TestInitialize; credentials are
  now the lab cert via SendCertificateOverMtls across all tests
- Remove all Console.WriteLine calls; diagnostic context is embedded in Assert messages
- MtlsBearerUserFlowTests: rename regional unit test to
  UserFlow_WithSendCertificateOverMtls_WithRegion_UsesRegionalMtlsEndpointAsync and clarify
  in XML doc that it is a general-purpose regional routing test (shared code path across all
  user flows), not an OBO-specific test
Copilot AI review requested due to automatic review settings May 19, 2026 15:40
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 6 out of 6 changed files in this pull request and generated 1 comment.

…est matrix

Adds the missing (OBO × client_secret) cell to the 2x2 grant/auth-mechanism matrix:

  | grant             | auth mechanism       | expected |
  |-------------------|----------------------|----------|
  | client_credentials| mTLS (no assertion)  | PASS     |
  | client_credentials| client_secret        | PASS     |
  | OBO               | mTLS (no assertion)  | FAIL*    |
  | OBO               | client_secret        | PASS ← new |

* Fails with AADSTS51000: MtlsClientAuth is/are disabled on AppWebApi.
  The baseline test proves OBO itself works; the mTLS failure is app-config only.

Also updates OboFlow_WithSendCertificateOverMtls_AcquiresTokenAsync XML doc to
cross-reference the 2x2 matrix and clarify expected failure reason.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Robbie-Microsoft Robbie-Microsoft marked this pull request as draft May 20, 2026 19:30
Bug #1 (cache crash on 2nd mTLS call):
- TokenCache.ITokenCacheInternal.cs: FilterTokensByEnvironmentAsync and
  FindRefreshTokenAsync used requestParams.AuthorityInfo for alias resolution.
  After ResolveAuthorityAsync(), AuthorityInfo.Host is 'mtlsauth.microsoft.com',
  which causes RegionAndMtlsDiscoveryProvider to throw MtlsPopNotSupportedForEnvironment.
- Fix: use requestParams.AuthorityManager.OriginalAuthority.AuthorityInfo (same
  pattern already applied to GetTenantProfilesAsync in this file).
- Added regression test: OboFlow_WithSendCertificateOverMtls_SecondCallDoesNotCrashAsync

Bug #2 (AcquireTokenSilent does not route RT redemption to mTLS endpoint):
- ClientApplicationBaseExecutor.cs: the AcquireTokenSilentParameters overload
  never called TryInitMtlsPopParametersAsync, so IsMtlsRequested=false and
  RT redemption went to login.microsoftonline.com instead of mtlsauth.microsoft.com.
- Fix: add TryInitMtlsPopParametersAsync call before CreateRequestContextAndLogVersionInfo.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 21, 2026 14:01
Copilot AI review requested due to automatic review settings May 21, 2026 15:24
Replace 'contact the ESTS team' with customer-facing preview notice.
Feature is in preview; AAD-side enablement is required but no self-serve
portal exists yet.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 19 out of 19 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt:6

  • This PR also removes the unshipped public APIs AcquireTokenParameterBuilderExtensions.WithCachePartitionKey(...) and .WithReservedScopes(...), which is a significant surface-area change not mentioned in the PR description (the description focuses on extending mTLS bearer transport). Please either document this removal in the PR description (with rationale) or split it into a separate PR to keep the change set focused.
Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation3
Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation3.AfterCredentialEvaluationAsync(Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext
Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.CredentialEvaluationContext(System.Security.Cryptography.X509Certificates.X509Certificate2 mtlsCertificate) -> void
Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.MtlsCertificate.get -> System.Security.Cryptography.X509Certificates.X509Certificate2

Comment thread src/client/Microsoft.Identity.Client/OAuth2/TokenClient.cs
Comment thread src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs Outdated
Comment thread tests/Microsoft.Identity.Test.Unit/PublicApiTests/MtlsBearerUserFlowTests.cs Outdated
Robbie-Microsoft and others added 2 commits May 22, 2026 15:51
Per Bogdan+Qi direction: for the preview drop, all flows send
client_assertion in the POST body even when SendCertificateOverMtls=true.
The cert authenticates at the TLS layer AND the body carries the assertion.
North Star (cert-only for S2S) is deferred pending ESTS changes.

- CredentialMaterialResolver: remove MtlsCertificate from Mtls mode
  condition; mTLS bearer no longer suppresses client_assertion for any flow
- Unit tests: rename 2 tests (drop 'NoClientAssertion'), flip all 3 to
  assert client_assertion IS present in body
- Integration tests: flip Condition 2 in all 4 BothMtlsConditionsMet tests
  (OBO, RT, auth_code, S2S) to assert client_assertion IS present

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- TokenCache: reword OriginalAuthority comment to clarify that tokens
  can be cached under mtlsauth.* but alias resolution must use login.*
  to avoid MtlsPopNotSupportedForEnvironment from the discovery provider
- MtlsBearerUserFlowTests: remove unused 'using Microsoft.Identity.Client.Utils'

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 22, 2026 19:59
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 20 out of 20 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (3)

tests/Microsoft.Identity.Test.Unit/PublicApiTests/MtlsBearerUserFlowTests.cs:135

  • Same issue as above: client_assertion is compared for exact equality by MockHttpMessageHandler. Asserting it equals "placeholder" is brittle/incorrect because MSAL will send a dynamically generated JWT.
                        ExpectedPostData = new Dictionary<string, string>
                        {
                            { OAuth2Parameter.ClientAssertionType, OAuth2AssertionType.JwtBearer },
                            { OAuth2Parameter.ClientAssertion, "placeholder" }
                        }

tests/Microsoft.Identity.Test.Unit/PublicApiTests/MtlsBearerUserFlowTests.cs:194

  • Same issue as above for refresh_token redemption: MockHttpMessageHandler will compare client_assertion exactly, so expecting "placeholder" will fail. Assert presence/non-empty instead of an exact value.
                        ExpectedPostData = new Dictionary<string, string>
                        {
                            { OAuth2Parameter.ClientId, TestConstants.ClientId },
                            { OAuth2Parameter.GrantType, OAuth2GrantType.RefreshToken },
                            { OAuth2Parameter.RefreshToken, fakeRefreshToken },
                            { OAuth2Parameter.ClientAssertionType, OAuth2AssertionType.JwtBearer },
                            { OAuth2Parameter.ClientAssertion, "placeholder" }
                        },

tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/MtlsTransportUserFlowTests.cs:168

  • This comment states "No client_assertion is sent in the body", which contradicts the test intent/other assertions in this file (mTLS transport preview still includes client_assertion in the body). Update the comment to avoid misleading future maintainers.
            // Build CCA with SendCertificateOverMtls=true: the cert authenticates at the TLS layer
            // and the factory provides the mTLS connection. No client_assertion is sent in the body.
            // NOTE: WithHttpClientFactory must come AFTER WithTestLogging to override the sniffer factory.
            var cca = ConfidentialClientApplicationBuilder

Comment thread docs/mtls-bearer-transport.md
Comment thread src/client/Microsoft.Identity.Client/OAuth2/TokenClient.cs
Robbie-Microsoft and others added 2 commits May 22, 2026 16:33
- MtlsBearerUserFlowTests: replace 'placeholder' in ExpectedPostData for
  client_assertion with AdditionalRequestValidation + StringAssert.Contains
  (handler does exact value match; MSAL generates a signed JWT not 'placeholder')
- MtlsTransportUserFlowTests: update stale inline comments that said
  'no client_assertion sent' to reflect preview behavior (cert at TLS + assertion)
- docs/mtls-bearer-transport.md: update description and verification
  section to match implemented preview behavior (assertion IS in body)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
These APIs were added in PR #6014 (022dcde) but were accidentally absent
from this branch due to shallow clone grafting. Restoring to fix CI build
failures in WithReservedScopesTests.cs and WithCachePartitionKeyTests.cs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 22, 2026 20:43
Robbie-Microsoft and others added 2 commits May 22, 2026 16:46
…om PR #6014

Restores AcquireTokenCommonParameters.SendOfflineAccessScope and all 6
PublicAPI.Unshipped.txt entries for WithCachePartitionKey and WithReservedScopes
that were lost due to a prior merge conflict resolution.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…RequestParameters

Restores the offline_access scope filtering logic in TokenClient.SendTokenRequestAsync
and the SendOfflineAccessScope property on AuthenticationRequestParameters, both
originally added in PR #6014 but lost in a prior merge conflict resolution.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 11 out of 11 changed files in this pull request and generated 6 comments.

Comment thread tests/Microsoft.Identity.Test.Unit/PublicApiTests/MtlsBearerUserFlowTests.cs Outdated
Comment thread docs/mtls-bearer-transport.md Outdated
Robbie-Microsoft and others added 2 commits May 22, 2026 17:05
- Lock LastCapturedUrl/LastCapturedBody reads in RecordingMtlsHttpClientFactory
  to prevent InvalidOperationException from concurrent MSAL HTTP calls
- Add ConfigureAwait(false) to sync-over-async ReadAsStringAsync calls in
  recording handler (integration tests) and AdditionalRequestValidation
  lambdas (unit tests) to eliminate deadlock risk
- Reword 'Windows only' limitation in docs to clarify it is the integration
  test setup that is Windows-only, not the feature itself

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…citBearerOverMtlsAsync

Case 2 handles the TokenBindingCertificate pattern where the assertion delegate
signals mTLS intent by returning a non-null cert. This is a separate opt-in
mechanism from SendCertificateOverMtls (Case 1) and must not require it.

The SendCertificateOverMtls guard was too broad and broke:
- BearerClientAssertion_WithPoPDelegate_Works
- BearerClientAssertion_WithPoPDelegate_CanReturnDifferentPairs...
- ClientAssertion_NotCalledWhenTokenFromCacheAsync
- WithMtlsAssertion_NoRegion_UsesGlobalEndpointAsync

Full suite: 2063 passed, 0 failed (up from 2059 passed, 4 failed).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 22, 2026 21:32
@Robbie-Microsoft Robbie-Microsoft requested review from Copilot and removed request for Copilot May 22, 2026 21:33
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 11 out of 11 changed files in this pull request and generated 1 comment.

Robbie-Microsoft and others added 2 commits May 22, 2026 17:42
…itializer

The comment previously stated 'called once per request' which was inaccurate.
Case 2 calls GetAssertionAsync once on every request (including cache hits) to
check for TokenBindingCertificate; GetCredentialMaterialAsync calls it a second
time on network requests to produce the JWT assertion. Both calls are intentional.
Delegates are expected to be cheap (return a pre-generated/cached assertion).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…p compatibility

When SendCertificateOverMtls=true, the client_assertion JWT is sent in the body
alongside the TLS certificate. For apps configured with SNI (Subject Name Issuer),
AAD cannot validate the JWT by cert thumbprint alone - it requires the x5c chain in
the JWT header to correlate the assertion with the SNI-registered certificate.

Without this fix, Sni_Over_Mtls_Gets_Bearer_Token_Successfully_TestAsync and
ClientCredentials_WithSendCertificateOverMtls_BothMtlsConditionsMet fail with:
  AADSTS700027: The certificate with identifier used to sign the client assertion
  is not registered on application. SNI may be configured on the app. Please
  ensure that client assertion is being sent with the x5c claim.

The fix: in CredentialMaterialResolver.BuildContext, OR SendX5C with
CertificateOptions.SendCertificateOverMtls so x5c is automatically included
in the JWT whenever bearer-over-mTLS transport is used.

Unit tests: 2063 passed, 0 failed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 22, 2026 22:24
… pending lab config

Both tests require mTLS to be enabled on lab apps in ID4SLAB1 by Bogdan/Qi:
- OboFlow_WithSendCertificateOverMtls_AcquiresTokenAsync: AADSTS700027 on AppWebApi (23c64cd8)
- RefreshTokenFlow_WithSendCertificateOverMtls_AcquiresTokenAsync: AADSTS392189 on AppS2S

[Ignore] attributes include error codes and instructions to remove once unblocked.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 11 out of 11 changed files in this pull request and generated 2 comments.

Add ClientId, GrantType=JwtBearer, and RequestedTokenUse=OnBehalfOf to the
ExpectedPostData of UserFlow_WithSendCertificateOverMtls_WithRegion_UsesRegionalMtlsEndpointAsync,
matching the coverage already present in OboFlow_WithSendCertificateOverMtls_UsesGlobalMtlsEndpointAsync.

This makes the regional routing test a stronger regression check — it now verifies
the full OBO POST body shape, not just the assertion type.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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