Skip to content

Commit aef501f

Browse files
gladjohnGladwinJohnsonlocalden
authored
Implementation for SNI + MTLS Flow in MSAL (#4965)
* very very draft * address comments * pr comments * validations and tests * test app * add unit tests * pr comments * pr comments * Apply suggestions from code review code comments Co-authored-by: Den Delimarsky <[email protected]> * Apply suggestions from code review Co-authored-by: Den Delimarsky <[email protected]> * gov cloud * consolidate * test fix * temp * disable test * test fix * enhance * test fix * IgnoreOnClassicPipeline * test updates * set ClientCredential to null * more tests * test update * UnExpectedPostData * address comments * pr comments * merge conflicts * pr comments * pr comments * publicapi * NonStandardCloud Mtls test * tenantidfix * public api * fix public api --------- Co-authored-by: Gladwin Johnson <[email protected]> Co-authored-by: Den Delimarsky <[email protected]>
1 parent eb59900 commit aef501f

File tree

74 files changed

+1325
-209
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+1325
-209
lines changed

src/client/Microsoft.Identity.Client/ApiConfig/AbstractConfidentialClientAcquireTokenParameterBuilder.cs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,9 @@
22
// Licensed under the MIT License.
33

44
using System;
5-
using System.Collections.Generic;
65
using System.ComponentModel;
7-
using System.Security.Cryptography.X509Certificates;
86
using System.Threading;
97
using System.Threading.Tasks;
10-
using Microsoft.Identity.Client.ApiConfig;
118
using Microsoft.Identity.Client.ApiConfig.Executors;
129
using Microsoft.Identity.Client.AppConfig;
1310
using Microsoft.Identity.Client.AuthScheme.PoP;
@@ -47,11 +44,11 @@ public override Task<AuthenticationResult> ExecuteAsync(CancellationToken cancel
4744
/// </summary>
4845
/// <exception cref="MsalClientException"></exception>
4946
protected override void Validate()
50-
{
47+
{
5148
// Confidential client must have a credential
5249
if (ServiceBundle?.Config.ClientCredential == null &&
5350
CommonParameters.OnBeforeTokenRequestHandler == null &&
54-
ServiceBundle?.Config.AppTokenProvider == null
51+
ServiceBundle?.Config.AppTokenProvider == null
5552
)
5653
{
5754
throw new MsalClientException(
@@ -73,12 +70,14 @@ protected override void Validate()
7370
/// <returns>The builder.</returns>
7471
/// <remarks>
7572
/// <list type="bullet">
76-
/// <item><description>An Authentication header is automatically added to the request.</description></item>
7773
/// <item><description>The PoP token is bound to the HTTP request, more specifically to the HTTP method (GET, POST, etc.) and to the Uri (path and query, but not query parameters).</description></item>
7874
/// <item><description>MSAL creates, reads and stores a key in memory that will be cycled every 8 hours.</description></item>
7975
/// <item><description>This is an experimental API. The method signature may change in the future without involving a major version upgrade.</description></item>
8076
/// </list>
8177
/// </remarks>
78+
[EditorBrowsable(EditorBrowsableState.Never)] // Soft deprecate
79+
[Obsolete("WithProofOfPossession is deprecated. Use WithSignedHttpRequestProofOfPossession for SHR Proof-of-Possession functionality. " +
80+
"For more details and to learn about other Proof-of-Possession MSAL supports, see the MSAL documentation: https://aka.ms/msal-net-pop")]
8281
public T WithProofOfPossession(PoPAuthenticationConfiguration popAuthenticationConfiguration)
8382
{
8483
ValidateUseOfExperimentalFeature();
@@ -89,5 +88,28 @@ public T WithProofOfPossession(PoPAuthenticationConfiguration popAuthenticationC
8988

9089
return this as T;
9190
}
91+
92+
/// <summary>
93+
/// Modifies the request to acquire a Signed HTTP Request (SHR) Proof-of-Possession (PoP) token, rather than a Bearer.
94+
/// SHR PoP tokens are bound to the HTTP request and to a cryptographic key, which MSAL manages on Windows.
95+
/// SHR PoP tokens are different from mTLS PoP tokens, which are used for Mutual TLS (mTLS) authentication. See <see href="https://aka.ms/mtls-pop"/> for details.
96+
/// </summary>
97+
/// <param name="popAuthenticationConfiguration">Configuration properties used to construct a Proof-of-Possession request.</param>
98+
/// <returns>The builder.</returns>
99+
/// <remarks>
100+
/// <list type="bullet">
101+
/// <item><description>The SHR PoP token is bound to the HTTP request, specifically to the HTTP method (for example, `GET` or `POST`) and to the URI path and query, excluding query parameters.</description></item>
102+
/// <item><description>MSAL creates, reads, and stores a key in memory that will be cycled every 8 hours.</description></item>
103+
/// <item><description>This is an experimental API. The method signature may change in the future without involving a major version upgrade.</description></item>
104+
/// </list>
105+
/// </remarks>
106+
public T WithSignedHttpRequestProofOfPossession(PoPAuthenticationConfiguration popAuthenticationConfiguration)
107+
{
108+
CommonParameters.PopAuthenticationConfiguration = popAuthenticationConfiguration ?? throw new ArgumentNullException(nameof(popAuthenticationConfiguration));
109+
110+
CommonParameters.AuthenticationOperation = new PopAuthenticationOperation(CommonParameters.PopAuthenticationConfiguration, ServiceBundle);
111+
112+
return this as T;
113+
}
92114
}
93115
}

src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenForClientParameterBuilder.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44
using System;
55
using System.Collections.Generic;
66
using System.ComponentModel;
7+
using System.Security.Cryptography.X509Certificates;
78
using System.Threading;
89
using System.Threading.Tasks;
910
using Microsoft.Identity.Client.ApiConfig.Executors;
1011
using Microsoft.Identity.Client.ApiConfig.Parameters;
12+
using Microsoft.Identity.Client.AuthScheme.PoP;
13+
using Microsoft.Identity.Client.Internal;
14+
using Microsoft.Identity.Client.Internal.ClientCredential;
1115
using Microsoft.Identity.Client.TelemetryCore.Internal.Events;
1216
using Microsoft.Identity.Client.Utils;
1317

@@ -74,6 +78,30 @@ public AcquireTokenForClientParameterBuilder WithSendX5C(bool withSendX5C)
7478
return this;
7579
}
7680

81+
/// <summary>
82+
/// Specifies that the certificate provided will be used for PoP tokens with mTLS (Mutual TLS) authentication.
83+
/// For more information, refer to the <see href="https://aka.ms/mtls-pop">Proof-of-Possession documentation</see>.
84+
/// </summary>
85+
/// <returns>The current instance of <see cref="AcquireTokenForClientParameterBuilder"/> to enable method chaining.</returns>
86+
public AcquireTokenForClientParameterBuilder WithMtlsProofOfPossession()
87+
{
88+
ValidateUseOfExperimentalFeature();
89+
90+
if (ServiceBundle.Config.ClientCredential is not CertificateClientCredential certificateCredential)
91+
{
92+
throw new MsalClientException(
93+
MsalError.MtlsCertificateNotProvided,
94+
MsalErrorMessage.MtlsCertificateNotProvidedMessage);
95+
}
96+
else
97+
{
98+
CommonParameters.AuthenticationOperation = new MtlsPopAuthenticationOperation(certificateCredential.Certificate);
99+
CommonParameters.MtlsCertificate = certificateCredential.Certificate;
100+
}
101+
102+
return this;
103+
}
104+
77105
/// <summary>
78106
/// Please use WithAzureRegion on the ConfidentialClientApplicationBuilder object
79107
/// </summary>
@@ -103,7 +131,34 @@ internal override Task<AuthenticationResult> ExecuteInternalAsync(CancellationTo
103131
/// <inheritdoc/>
104132
protected override void Validate()
105133
{
134+
if (CommonParameters.MtlsCertificate != null)
135+
{
136+
string authorityUri = ServiceBundle.Config.Authority.AuthorityInfo.CanonicalAuthority.AbsoluteUri;
137+
138+
if (ServiceBundle.Config.Authority.AuthorityInfo.AuthorityType != AuthorityType.Aad)
139+
{
140+
throw new MsalClientException(
141+
MsalError.InvalidAuthorityType,
142+
MsalErrorMessage.MtlsInvalidAuthorityTypeMessage);
143+
}
144+
145+
if (authorityUri.Contains("/common", StringComparison.OrdinalIgnoreCase))
146+
{
147+
throw new MsalClientException(
148+
MsalError.MissingTenantedAuthority,
149+
MsalErrorMessage.MtlsNonTenantedAuthorityNotAllowedMessage);
150+
}
151+
152+
if (string.IsNullOrEmpty(ServiceBundle.Config.AzureRegion))
153+
{
154+
throw new MsalClientException(
155+
MsalError.MtlsPopWithoutRegion,
156+
MsalErrorMessage.MtlsPopWithoutRegion);
157+
}
158+
}
159+
106160
base.Validate();
161+
107162
if (Parameters.SendX5C == null)
108163
{
109164
Parameters.SendX5C = this.ServiceBundle.Config.SendX5C;

src/client/Microsoft.Identity.Client/ApiConfig/Executors/AbstractExecutor.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Globalization;
6+
using System.Security.Cryptography.X509Certificates;
67
using System.Threading;
78
using Microsoft.Identity.Client.Core;
89
using Microsoft.Identity.Client.Internal;
@@ -19,9 +20,9 @@ protected AbstractExecutor(IServiceBundle serviceBundle)
1920

2021
public IServiceBundle ServiceBundle { get; }
2122

22-
protected RequestContext CreateRequestContextAndLogVersionInfo(Guid correlationId, CancellationToken userCancellationToken = default)
23+
protected RequestContext CreateRequestContextAndLogVersionInfo(Guid correlationId, X509Certificate2 mtlsCertificate, CancellationToken userCancellationToken = default)
2324
{
24-
var requestContext = new RequestContext(ServiceBundle, correlationId, userCancellationToken);
25+
var requestContext = new RequestContext(ServiceBundle, correlationId, mtlsCertificate, userCancellationToken);
2526

2627
requestContext.Logger.Info(
2728
() => string.Format(

src/client/Microsoft.Identity.Client/ApiConfig/Executors/ClientApplicationBaseExecutor.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public async Task<AuthenticationResult> ExecuteAsync(
2828
AcquireTokenSilentParameters silentParameters,
2929
CancellationToken cancellationToken)
3030
{
31-
var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, cancellationToken);
31+
var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
3232

3333
var requestParameters = await _clientApplicationBase.CreateRequestParametersAsync(
3434
commonParameters,
@@ -46,7 +46,7 @@ public async Task<AuthenticationResult> ExecuteAsync(
4646
AcquireTokenByRefreshTokenParameters refreshTokenParameters,
4747
CancellationToken cancellationToken)
4848
{
49-
var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, cancellationToken);
49+
var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
5050
if (commonParameters.Scopes == null || !commonParameters.Scopes.Any())
5151
{
5252
commonParameters.Scopes = new SortedSet<string>

src/client/Microsoft.Identity.Client/ApiConfig/Executors/ConfidentialClientExecutor.cs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Threading;
66
using System.Threading.Tasks;
77
using Microsoft.Identity.Client.ApiConfig.Parameters;
8+
using Microsoft.Identity.Client.AuthScheme.PoP;
89
using Microsoft.Identity.Client.Instance.Discovery;
910
using Microsoft.Identity.Client.Internal;
1011
using Microsoft.Identity.Client.Internal.Requests;
@@ -33,9 +34,9 @@ public async Task<AuthenticationResult> ExecuteAsync(
3334
AcquireTokenByAuthorizationCodeParameters authorizationCodeParameters,
3435
CancellationToken cancellationToken)
3536
{
36-
var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, cancellationToken);
37+
RequestContext requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
3738

38-
var requestParams = await _confidentialClientApplication.CreateRequestParametersAsync(
39+
AuthenticationRequestParameters requestParams = await _confidentialClientApplication.CreateRequestParametersAsync(
3940
commonParameters,
4041
requestContext,
4142
_confidentialClientApplication.UserTokenCacheInternal).ConfigureAwait(false);
@@ -54,9 +55,9 @@ public async Task<AuthenticationResult> ExecuteAsync(
5455
AcquireTokenForClientParameters clientParameters,
5556
CancellationToken cancellationToken)
5657
{
57-
var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, cancellationToken);
58+
RequestContext requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
5859

59-
var requestParams = await _confidentialClientApplication.CreateRequestParametersAsync(
60+
AuthenticationRequestParameters requestParams = await _confidentialClientApplication.CreateRequestParametersAsync(
6061
commonParameters,
6162
requestContext,
6263
_confidentialClientApplication.AppTokenCacheInternal).ConfigureAwait(false);
@@ -76,9 +77,9 @@ public async Task<AuthenticationResult> ExecuteAsync(
7677
AcquireTokenOnBehalfOfParameters onBehalfOfParameters,
7778
CancellationToken cancellationToken)
7879
{
79-
var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, cancellationToken);
80+
RequestContext requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
8081

81-
var requestParams = await _confidentialClientApplication.CreateRequestParametersAsync(
82+
AuthenticationRequestParameters requestParams = await _confidentialClientApplication.CreateRequestParametersAsync(
8283
commonParameters,
8384
requestContext,
8485
_confidentialClientApplication.UserTokenCacheInternal).ConfigureAwait(false);
@@ -100,9 +101,9 @@ public async Task<Uri> ExecuteAsync(
100101
GetAuthorizationRequestUrlParameters authorizationRequestUrlParameters,
101102
CancellationToken cancellationToken)
102103
{
103-
var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, cancellationToken);
104+
RequestContext requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
104105

105-
var requestParameters = await _confidentialClientApplication.CreateRequestParametersAsync(
106+
AuthenticationRequestParameters requestParameters = await _confidentialClientApplication.CreateRequestParametersAsync(
106107
commonParameters,
107108
requestContext,
108109
_confidentialClientApplication.UserTokenCacheInternal).ConfigureAwait(false);
@@ -136,9 +137,9 @@ public async Task<AuthenticationResult> ExecuteAsync(
136137
AcquireTokenByUsernamePasswordParameters usernamePasswordParameters,
137138
CancellationToken cancellationToken)
138139
{
139-
var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, cancellationToken);
140+
RequestContext requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
140141

141-
var requestParams = await _confidentialClientApplication.CreateRequestParametersAsync(
142+
AuthenticationRequestParameters requestParams = await _confidentialClientApplication.CreateRequestParametersAsync(
142143
commonParameters,
143144
requestContext,
144145
_confidentialClientApplication.UserTokenCacheInternal).ConfigureAwait(false);

src/client/Microsoft.Identity.Client/ApiConfig/Executors/ManagedIdentityExecutor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public async Task<AuthenticationResult> ExecuteAsync(
3333
AcquireTokenForManagedIdentityParameters managedIdentityParameters,
3434
CancellationToken cancellationToken)
3535
{
36-
var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, cancellationToken);
36+
var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
3737

3838
var requestParams = await _managedIdentityApplication.CreateRequestParametersAsync(
3939
commonParameters,

src/client/Microsoft.Identity.Client/ApiConfig/Executors/PublicClientExecutor.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public async Task<AuthenticationResult> ExecuteAsync(
2626
AcquireTokenInteractiveParameters interactiveParameters,
2727
CancellationToken cancellationToken)
2828
{
29-
var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, cancellationToken);
29+
var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
3030

3131
AuthenticationRequestParameters requestParams = await _publicClientApplication.CreateRequestParametersAsync(
3232
commonParameters,
@@ -47,7 +47,7 @@ public async Task<AuthenticationResult> ExecuteAsync(
4747
AcquireTokenWithDeviceCodeParameters deviceCodeParameters,
4848
CancellationToken cancellationToken)
4949
{
50-
var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, cancellationToken);
50+
var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
5151

5252
var requestParams = await _publicClientApplication.CreateRequestParametersAsync(
5353
commonParameters,
@@ -67,7 +67,7 @@ public async Task<AuthenticationResult> ExecuteAsync(
6767
AcquireTokenByIntegratedWindowsAuthParameters integratedWindowsAuthParameters,
6868
CancellationToken cancellationToken)
6969
{
70-
var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, cancellationToken);
70+
var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
7171

7272
var requestParams = await _publicClientApplication.CreateRequestParametersAsync(
7373
commonParameters,
@@ -87,7 +87,7 @@ public async Task<AuthenticationResult> ExecuteAsync(
8787
AcquireTokenByUsernamePasswordParameters usernamePasswordParameters,
8888
CancellationToken cancellationToken)
8989
{
90-
var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, cancellationToken);
90+
var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
9191

9292
var requestParams = await _publicClientApplication.CreateRequestParametersAsync(
9393
commonParameters,

src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AcquireTokenForClientParameters.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4+
using System.Security.Cryptography.X509Certificates;
45
using System.Text;
56
using Microsoft.Identity.Client.Core;
67

src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,9 @@ public string ClientVersion
124124

125125
public Func<AppTokenProviderParameters, Task<AppTokenProviderResult>> AppTokenProvider;
126126

127-
#region ClientCredentials
127+
#region ClientCredentials
128128

129+
// Indicates if claims or assertions are used within the configuration
129130
public IClientCredential ClientCredential { get; internal set; }
130131

131132
/// <summary>

0 commit comments

Comments
 (0)