MTLS Without Tokens Support - MicrosoftIdentityMessageHandler Support#3815
MTLS Without Tokens Support - MicrosoftIdentityMessageHandler Support#3815tlupes wants to merge 18 commits into
Conversation
neha-bhargava
left a comment
There was a problem hiding this comment.
Left some comments
| <ItemGroup Condition="'$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'net472' or '$(TargetFramework)' == 'netstandard2.0'"> | ||
| <Compile Include="..\Shared\CodeAnalysisAttributes.cs" Link="Polyfills\CodeAnalysisAttributes.cs" /> | ||
| </ItemGroup> | ||
| <ItemGroup> |
There was a problem hiding this comment.
Why is this removed?
There was a problem hiding this comment.
With the added InternalsVisibleTo it's no longer needed (and in fact results in errors).
Co-authored-by: Neha Bhargava <[email protected]>
…essageHandler.cs Co-authored-by: Neha Bhargava <[email protected]>
Co-authored-by: Neha Bhargava <[email protected]>
…com/AzureAD/microsoft-identity-web into MicrosoftIdentityMessageHandlerMtls
| var httpClientFactory = services.GetService<IHttpClientFactory>(); | ||
| if (httpClientFactory is not null) | ||
| { | ||
| return new MsalMtlsHttpClientFactory(httpClientFactory); |
There was a problem hiding this comment.
I don't know how this helps. All mTLS traffic will bypass the factory. I guess it's fine, I don't have an alternative for this. Maybe log a warning?
There was a problem hiding this comment.
It's trying to handle scenarios where not all services are available, but some are. In this, if IMsalMtlsHttpClientFactory is available, then it will use that. Failing that, then it will check if IHttpClientFactory is available and use that. Failing that, it returns null.
| { | ||
| if (_credentialsProvider is null) | ||
| { | ||
| throw new InvalidOperationException("mTLS authentication requires a Credentials Provider object to be registered, but no such service was found."); |
There was a problem hiding this comment.
Consider adding an aka.ms/idweb/mtls page where you describe the sceanrios in more detail. As an app developer, I wouldn't know how to action this exception.
There was a problem hiding this comment.
Added a link, I will make the link work in the near future. Once this is in I'll write up a help page for it.
| { | ||
| if (_mtlsHttpClientFactory is null) | ||
| { | ||
| throw new InvalidOperationException("Authentication using mTLS requires a MtlsHttpClientFactory object to be registered, but no such service was found."); |
There was a problem hiding this comment.
A help page would be good.
BUt at the same time, can the app developer get into this state? I thought we provide an HTTP pipeline in all cases.
There was a problem hiding this comment.
HTTP pipeline will not be present if the consumer did not register an http factory (e.g. services.AddHttp();)
… dispatch - Add missing 'using Microsoft.Identity.Web.Diagnostics' to DownstreamApi.cs so UnauthorizedHttpRequestException resolves. - Fix Invoke()/invoke() casing in CertificatesObserverTests (3 sites). - Rename SendAsync_WithMtlsPop_NullMtlsFactory_UsesBaseSendAsync to *_Throws and assert InvalidOperationException, matching the deliberate behavior change introduced when the mTLS dispatch was tightened to require an IMsalMtlsHttpClientFactory whenever a binding certificate is present. - Wire SendAsync_CertificateFailureRetry_ClonesRequest through a real mTLS factory so the retry-clone path it covers is reachable. Co-authored-by: Copilot <[email protected]>
…g with internal cert-failure retry For ProtocolScheme=MTLS there is no token to refresh with WWW-Authenticate claims, and SendWithCertRetryAsync already retried credential acquisition on the initial 401. Allowing the outer claims-challenge retry to also fire would issue a redundant third send and another GetCredentialAsync call, which may hit disk or Key Vault, for no semantic benefit. Add SendAsync_MtlsOnly_PersistentUnauthorizedWithClaims_DoesNotComposeRetries to pin the gating: exactly two sends and two GetCredentialAsync calls when the server keeps returning 401 + claims challenge in pure-mTLS mode. Co-authored-by: Copilot <[email protected]>
There was a problem hiding this comment.
Pull request overview
Extends the existing mTLS “without tokens” work (previously added for DownstreamApi) to MicrosoftIdentityMessageHandler, including DI wiring and shared-constant refactoring into Microsoft.Identity.Web.TokenAcquisition.
Changes:
- Adds mTLS-only dispatch + bounded cert-failure retry behavior to
MicrosoftIdentityMessageHandler, and updates tests accordingly. - Refactors shared protocol/status-code constants into
Microsoft.Identity.Web.TokenAcquisition/Constants.csand updates DownstreamApi/TokenAcquisition to consume them. - Updates HttpClient builder extensions to resolve configured
IMsalMtlsHttpClientFactory, pass a logger, and optionally construct/use anICredentialsProvider.
Reviewed changes
Copilot reviewed 13 out of 14 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Microsoft.Identity.Web.Test/MicrosoftIdentityMessageHandlerTokenBindingTests.cs | Updates/extends handler tests for mTLS-only retry and cloning behavior. |
| tests/Microsoft.Identity.Web.Test/MicrosoftIdentityHttpClientBuilderExtensionsTests.cs | Minor formatting change in tests. |
| tests/Microsoft.Identity.Web.Test/CertificatesObserverTests.cs | Expands observer test coverage to include handler-based mTLS path. |
| src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs | Updates reference to renamed certificate-related STS error-code constant. |
| src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/NetFramework/PublicAPI.Unshipped.txt | Records new public handler constructor overload for .NET Framework targets. |
| src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/NetCore/PublicAPI.Unshipped.txt | Records new public handler constructor overload for .NET (Core) targets. |
| src/Microsoft.Identity.Web.TokenAcquisition/Properties/InternalsVisibleTo.cs | Grants DownstreamApi access to TokenAcquisition internals used by the refactor. |
| src/Microsoft.Identity.Web.TokenAcquisition/MicrosoftIdentityMessageHandler.cs | Implements mTLS-only support + cert-failure retry orchestration in the handler. |
| src/Microsoft.Identity.Web.TokenAcquisition/MicrosoftIdentityHttpClientBuilderExtensions.cs | Improves DI resolution for logger, mTLS factory, and credentials provider. |
| src/Microsoft.Identity.Web.TokenAcquisition/GlobalSuppressions.cs | Adds analyzer suppression for the new overload signature. |
| src/Microsoft.Identity.Web.TokenAcquisition/Constants.cs | Centralizes protocol names and auth-failure status code set. |
| src/Microsoft.Identity.Web.DownstreamApi/Microsoft.Identity.Web.DownstreamApi.csproj | Removes AOT attribute polyfill include (affects older TFMs). |
| src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs | Switches to shared constants and uses moved diagnostics exception. |
| src/Microsoft.Identity.Web.Diagnostics/UnauthorizedHttpRequestException.cs | Introduces moved exception type used for certificate-failure reporting. |
Comments suppressed due to low confidence (1)
src/Microsoft.Identity.Web.DownstreamApi/Microsoft.Identity.Web.DownstreamApi.csproj:18
Microsoft.Identity.Web.DownstreamApitargetsnet462/net472/netstandard2.0(see the PublicAPI conditional), but the csproj no longer linkssrc/Shared/CodeAnalysisAttributes.cs. This file polyfillsRequiresUnreferencedCodeAttribute/RequiresDynamicCodeAttributefor NETFRAMEWORK/NETSTANDARD2_0, and DownstreamApi uses these attributes unconditionally inDownstreamApi.cs. Without the polyfill, older TFMs will fail to compile. Re-add the conditional<Compile Include="..\Shared\CodeAnalysisAttributes.cs" ...>item group (as done in TokenAcquisition) or gate the attribute usages behind#iffor TFMs that provide them.
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="MicrosoftIdentityMessageHandler"/> class with mTLS PoP token binding support. | ||
| /// </summary> | ||
| /// <param name="headerProvider"> | ||
| /// The <see cref="IAuthorizationHeaderProvider"/> used to acquire authorization headers for outgoing requests. | ||
| /// This is typically obtained from the dependency injection container. | ||
| /// </param> | ||
| /// <param name="defaultOptions"> | ||
| /// Default authentication options that will be used for all requests unless overridden per-request. | ||
| /// If <see langword="null"/>, each request must specify its own authentication options or an exception will be thrown. | ||
| /// </param> | ||
| /// <param name="mtlsHttpClientFactory"> | ||
| /// Optional factory for creating HTTP clients configured with mTLS client certificates for token binding | ||
| /// (mTLS PoP) scenarios. When provided and the <see cref="AuthorizationHeaderProviderOptions.ProtocolScheme"/> | ||
| /// is set to <c>"MTLS_POP"</c>, the handler will use this factory to create an HTTP client with the binding | ||
| /// certificate and send requests through it. | ||
| /// </param> | ||
| /// <param name="credentialsProvider"> | ||
| /// Optional provider for certificates. This is required for mTLS-only authentication purposes. | ||
| /// </param> |
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
…com/AzureAD/microsoft-identity-web into MicrosoftIdentityMessageHandlerMtls
MTLS Without Tokens Support - MicrosoftIdentityMessageHandler Support
Continuation of the PR #3747 , this adds the same level of support to MicrosoftIdentityMessageHandler.
Description
Microsoft.Identity.Web.TokenAcquisition/Constants.csMicrosoft.Identity.Web.TokenAcquisition(Internal class)Microsoft.Identity.Web.TokenAcquisitionandMicrosoft.Identity.Web.DownstreamApiso that the above changes are accessible.MicrosoftIdentityHttpClientBuilderExtensions:MicrosoftIdentityMessageHandler. Now, it will pass one if configured.IMsalMtlsHttpFactoryeven if one were configured. Now, it will use a configured one if available.