diff --git a/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder.cs b/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder.cs index 04995d6614..14665d317f 100644 --- a/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder.cs +++ b/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder.cs @@ -56,6 +56,24 @@ internal static AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder Cr .WithScopes(scopes); } + /// + /// Applicable to first-party applications only, this method also allows to specify + /// if the x5c claim should be sent to Azure AD. + /// Sending the x5c enables application developers to achieve easy certificate roll-over in Azure AD: + /// this method will send the certificate chain to Azure AD along with the token request, + /// so that Azure AD can use it to validate the subject name based on a trusted issuer policy. + /// This saves the application admin from the need to explicitly manage the certificate rollover + /// (either via portal or PowerShell/CLI operation). For details see https://aka.ms/msal-net-sni + /// + /// true if the x5c should be sent. Otherwise false. + /// The default is false + /// The builder to chain the .With methods + public AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder WithSendX5C(bool withSendX5C) + { + Parameters.SendX5C = withSendX5C; + return this; + } + /// internal override Task ExecuteInternalAsync(CancellationToken cancellationToken) { diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt index 5f282702bb..ef9185312d 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ - \ No newline at end of file +Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder.WithSendX5C(bool withSendX5C) -> Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder + diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt index 5f282702bb..ef9185312d 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ - \ No newline at end of file +Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder.WithSendX5C(bool withSendX5C) -> Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder + diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt index 5f282702bb..ef9185312d 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ - \ No newline at end of file +Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder.WithSendX5C(bool withSendX5C) -> Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder + diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt index 5f282702bb..ef9185312d 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ - \ No newline at end of file +Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder.WithSendX5C(bool withSendX5C) -> Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder + diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt index 5f282702bb..ef9185312d 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ - \ No newline at end of file +Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder.WithSendX5C(bool withSendX5C) -> Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder + diff --git a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt index 5f282702bb..ef9185312d 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ - \ No newline at end of file +Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder.WithSendX5C(bool withSendX5C) -> Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder + diff --git a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs index 67490087db..5e03718004 100644 --- a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs +++ b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs @@ -819,6 +819,43 @@ public async Task RopcCcaSendsX5CAsync(bool sendX5C) } } + [DataTestMethod] + [DataRow(true)] + [DataRow(false)] + public async Task RopcCcaSendsX5CUsingRequestLevelAPIAsync(bool sendX5C) + { + using (var harness = CreateTestHarness()) + { + var certificate = CertHelper.GetOrCreateTestCert(); + var exportedCertificate = Convert.ToBase64String(certificate.Export(X509ContentType.Cert)); + + var app = ConfidentialClientApplicationBuilder + .Create(TestConstants.ClientId) + .WithHttpManager(harness.HttpManager) + .WithCertificate(certificate) + .Build(); + + harness.HttpManager.AddInstanceDiscoveryMockHandler(); + + harness.HttpManager.AddMockHandler( + CreateTokenResponseHttpHandlerWithX5CValidation( + clientCredentialFlow: false, + expectedX5C: sendX5C ? exportedCertificate : null)); + + var result = await (app as IByUsernameAndPassword) + .AcquireTokenByUsernamePassword( + TestConstants.s_scope, + TestConstants.Username, + TestConstants.DefaultPassword) + .WithSendX5C(sendX5C) + .ExecuteAsync() + .ConfigureAwait(false); + + Assert.IsNotNull(result); + Assert.AreEqual(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource); + } + } + [TestMethod] public async Task EnsureCertificateSerialNumberIsAddedToCacheKeyTestAsync() {