From 984546ac816dcf80c5861e086933f902501a2e39 Mon Sep 17 00:00:00 2001 From: "Sruthi Keerthi Rangavajhula (from Dev Box)" Date: Thu, 20 Feb 2025 07:35:08 -0800 Subject: [PATCH 1/9] Send req_cnf for AtPop --- .../MsAuth10AtPop.cs | 8 ++++++-- tests/DevApps/aspnet-mvc/OwinWebApi/Web.config | 14 +++++++------- .../MsAuth10AtPopTests.cs | 6 +++--- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/MsAuth10AtPop.cs b/src/Microsoft.Identity.Web.TokenAcquisition/MsAuth10AtPop.cs index dee57e616..31b3102c6 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/MsAuth10AtPop.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/MsAuth10AtPop.cs @@ -36,8 +36,12 @@ internal static AcquireTokenForClientParameterBuilder WithAtPop( clientId, sendX5C); - data.BodyParameters.Remove("client_assertion"); - data.BodyParameters.Add("request", signedAssertion); + //data.BodyParameters.Remove("client_assertion"); + //data.BodyParameters.Add("request", signedAssertion); + + data.BodyParameters["client_assertion"] = signedAssertion; + data.BodyParameters.Add("req_cnf", Base64UrlEncoder.Encode(jwkClaim)); + data.BodyParameters.Add("token_type", "pop"); return Task.CompletedTask; }); diff --git a/tests/DevApps/aspnet-mvc/OwinWebApi/Web.config b/tests/DevApps/aspnet-mvc/OwinWebApi/Web.config index 2d99d4971..4c9a36187 100644 --- a/tests/DevApps/aspnet-mvc/OwinWebApi/Web.config +++ b/tests/DevApps/aspnet-mvc/OwinWebApi/Web.config @@ -58,11 +58,11 @@ - + - + @@ -74,7 +74,7 @@ - + @@ -82,19 +82,19 @@ - + - + - + - + diff --git a/tests/Microsoft.Identity.Web.Test/MsAuth10AtPopTests.cs b/tests/Microsoft.Identity.Web.Test/MsAuth10AtPopTests.cs index 4473a429c..cf8b0f3f2 100644 --- a/tests/Microsoft.Identity.Web.Test/MsAuth10AtPopTests.cs +++ b/tests/Microsoft.Identity.Web.Test/MsAuth10AtPopTests.cs @@ -53,12 +53,12 @@ public async Task MsAuth10AtPop_WithAtPop_ShouldPopulateBuilderWithProofOfPosess // Assert httpTokenRequest.ActualRequestPostData.TryGetValue("request", out string? request); - Assert.NotNull(request); + Assert.Null(request); httpTokenRequest.ActualRequestPostData.TryGetValue("client_assertion", out string? clientAssertion); - Assert.Null(clientAssertion); + Assert.NotNull(clientAssertion); JwtSecurityTokenHandler jwtSecurityTokenHandler = new JwtSecurityTokenHandler(); - JwtSecurityToken assertion = jwtSecurityTokenHandler.ReadJwtToken(request); + JwtSecurityToken assertion = jwtSecurityTokenHandler.ReadJwtToken(clientAssertion); Assert.Equal("https://login.microsoftonline.com/common/oauth2/v2.0/token", assertion.Claims.Single(c => c.Type == "aud").Value); Assert.Equal(TestConstants.ClientId, assertion.Claims.Single(c => c.Type == "iss").Value); From aa8e7923ea0b88e1dfae0bbc6accb3bcd259e176 Mon Sep 17 00:00:00 2001 From: "Sruthi Keerthi Rangavajhula (from Dev Box)" Date: Wed, 5 Mar 2025 18:22:36 -0800 Subject: [PATCH 2/9] Configure AtPop with custom signed assertion --- .../MsAuth10AtPop.cs | 51 +++++++++++++------ .../net7.0/InternalAPI.Unshipped.txt | 1 + .../TokenAcquisition.cs | 43 ++++++++++++++-- 3 files changed, 75 insertions(+), 20 deletions(-) diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/MsAuth10AtPop.cs b/src/Microsoft.Identity.Web.TokenAcquisition/MsAuth10AtPop.cs index 31b3102c6..79eed59df 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/MsAuth10AtPop.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/MsAuth10AtPop.cs @@ -3,9 +3,9 @@ using System; using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; +using Microsoft.Identity.Abstractions; using Microsoft.Identity.Client; using Microsoft.Identity.Client.Extensibility; using Microsoft.IdentityModel.JsonWebTokens; @@ -15,6 +15,7 @@ namespace Microsoft.Identity.Web { internal static class MsAuth10AtPop { + // Configure with Certificate internal static AcquireTokenForClientParameterBuilder WithAtPop( this AcquireTokenForClientParameterBuilder builder, X509Certificate2 clientCertificate, @@ -28,23 +29,43 @@ internal static AcquireTokenForClientParameterBuilder WithAtPop( builder.WithProofOfPosessionKeyId(popPublicKey); builder.OnBeforeTokenRequest((data) => - { - string? signedAssertion = GetSignedClientAssertion( - clientCertificate, - data.RequestUri.AbsoluteUri, - jwkClaim, - clientId, - sendX5C); + { + string? signedAssertion = GetSignedClientAssertion( + clientCertificate, + data.RequestUri.AbsoluteUri, + jwkClaim, + clientId, + sendX5C); + + data.BodyParameters.Remove("client_assertion"); + data.BodyParameters.Add("request", signedAssertion); - //data.BodyParameters.Remove("client_assertion"); - //data.BodyParameters.Add("request", signedAssertion); + return Task.CompletedTask; + }); + + return builder; + } - data.BodyParameters["client_assertion"] = signedAssertion; - data.BodyParameters.Add("req_cnf", Base64UrlEncoder.Encode(jwkClaim)); - data.BodyParameters.Add("token_type", "pop"); + // Configure with Custom Signed Assertion + internal static AcquireTokenForClientParameterBuilder WithAtPop( + this AcquireTokenForClientParameterBuilder builder, + CredentialDescription credentialDescription, + string popPublicKey, + string jwkClaim) + { + _ = Throws.IfNull(popPublicKey); + _ = Throws.IfNull(jwkClaim); + + builder.WithProofOfPosessionKeyId(popPublicKey); + builder.OnBeforeTokenRequest((data) => + { + string? signedAssertion = credentialDescription.CachedValue as string; + data.BodyParameters["client_assertion"] = signedAssertion; + data.BodyParameters.Add("req_cnf", Base64UrlEncoder.Encode(jwkClaim)); + data.BodyParameters.Add("token_type", "pop"); - return Task.CompletedTask; - }); + return Task.CompletedTask; + }); return builder; } diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Unshipped.txt index e69de29bb..8fd264ecc 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Unshipped.txt @@ -0,0 +1 @@ +static Microsoft.Identity.Web.MsAuth10AtPop.WithAtPop(this Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, Microsoft.Identity.Abstractions.CredentialDescription! credentialDescription, string! popPublicKey, string! jwkClaim) -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! \ No newline at end of file diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs index bb02d76aa..62a1a71d1 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs @@ -583,11 +583,11 @@ public async Task GetAuthenticationResultForAppAsync( _logger.LogWarning("MSAuth POP configured with pinned certificate. This configuration is being deprecated."); } - builder.WithAtPop( - application.AppConfig.ClientCredentialCertificate, - tokenAcquisitionOptions.PopPublicKey!, - tokenAcquisitionOptions.PopClaim!, - application.AppConfig.ClientId, + ConfigureAtPopWithCredentials( + builder, + application.AppConfig, + tokenAcquisitionOptions, + mergedOptions.ClientCredentials, mergedOptions.SendX5C); } } @@ -624,6 +624,39 @@ public async Task GetAuthenticationResultForAppAsync( } } + private static void ConfigureAtPopWithCredentials( + AcquireTokenForClientParameterBuilder builder, + IAppConfig appConfig, + TokenAcquisitionOptions tokenAcquisitionOptions, + IEnumerable? clientCredentials, + bool sendX5C) + { + // Try to configure AtPop with custom signed assertion first + if (clientCredentials != null) + { + foreach (var credential in clientCredentials) + { + if (credential.SourceType == CredentialSource.CustomSignedAssertion && + credential.CachedValue != null) + { + builder.WithAtPop( + credential, + tokenAcquisitionOptions.PopPublicKey!, + tokenAcquisitionOptions.PopClaim!); + return; + } + } + } + + // Fall back to certificate-based AtPop configuration + builder.WithAtPop( + appConfig.ClientCredentialCertificate, + tokenAcquisitionOptions.PopPublicKey!, + tokenAcquisitionOptions.PopClaim!, + appConfig.ClientId, + sendX5C); + } + /// /// Acquires a token from the authority configured in the app, for the confidential client itself (not on behalf of a user) /// using the client credentials flow. See https://aka.ms/msal-net-client-credentials. From 854d7fb9f391185767c74009b1cc714033c9a9ff Mon Sep 17 00:00:00 2001 From: "Sruthi Keerthi Rangavajhula (from Dev Box)" Date: Sun, 23 Mar 2025 08:11:28 -0700 Subject: [PATCH 3/9] Use WithAuthenticationExtension --- .../AtPopOperation.cs | 42 +++++++++ .../MsAuth10AtPop.cs | 93 +------------------ .../net462/InternalAPI.Unshipped.txt | 9 ++ .../net472/InternalAPI.Unshipped.txt | 9 ++ .../net6.0/InternalAPI.Unshipped.txt | 9 ++ .../net7.0/InternalAPI.Unshipped.txt | 10 +- .../net8.0/InternalAPI.Unshipped.txt | 9 ++ .../net9.0/InternalAPI.Unshipped.txt | 9 ++ .../netstandard2.0/InternalAPI.Unshipped.txt | 9 ++ .../TokenAcquisition.cs | 42 +-------- .../Mocks/MockHttpCreator.cs | 12 ++- .../MsAuth10AtPopTests.cs | 45 ++++----- 12 files changed, 137 insertions(+), 161 deletions(-) create mode 100644 src/Microsoft.Identity.Web.TokenAcquisition/AtPopOperation.cs diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/AtPopOperation.cs b/src/Microsoft.Identity.Web.TokenAcquisition/AtPopOperation.cs new file mode 100644 index 000000000..68627e1f9 --- /dev/null +++ b/src/Microsoft.Identity.Web.TokenAcquisition/AtPopOperation.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Microsoft.Identity.Client.AuthScheme; +using Microsoft.Identity.Client; +using Microsoft.IdentityModel.Tokens; + +namespace Microsoft.Identity.Web +{ + internal class AtPopOperation : IAuthenticationOperation + { + private readonly string _reqCnf; + + public AtPopOperation(string keyId, string reqCnf) + { + KeyId = keyId; + _reqCnf = reqCnf; + } + public int TelemetryTokenType => 4; // as per TelemetryTokenTypeConstants + + public string AuthorizationHeaderPrefix => "Bearer"; // these tokens go over bearer + + public string KeyId { get; } + + public string AccessTokenType => "pop"; // eSTS returns token_type=pop and MSAL needs to know + + public void FormatResult(AuthenticationResult authenticationResult) + { + // no-op, adding the SHR is done by the caller + } + + public IReadOnlyDictionary GetTokenRequestParams() + { + return new Dictionary() + { + {"req_cnf", Base64UrlEncoder.Encode(_reqCnf) }, + {"token_type", "pop" } + }; + } + } +} diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/MsAuth10AtPop.cs b/src/Microsoft.Identity.Web.TokenAcquisition/MsAuth10AtPop.cs index 79eed59df..d255f776f 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/MsAuth10AtPop.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/MsAuth10AtPop.cs @@ -1,110 +1,27 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using System; -using System.Collections.Generic; -using System.Security.Cryptography.X509Certificates; -using System.Threading.Tasks; -using Microsoft.Identity.Abstractions; using Microsoft.Identity.Client; using Microsoft.Identity.Client.Extensibility; -using Microsoft.IdentityModel.JsonWebTokens; -using Microsoft.IdentityModel.Tokens; namespace Microsoft.Identity.Web { internal static class MsAuth10AtPop { - // Configure with Certificate internal static AcquireTokenForClientParameterBuilder WithAtPop( this AcquireTokenForClientParameterBuilder builder, - X509Certificate2 clientCertificate, - string popPublicKey, - string jwkClaim, - string clientId, - bool sendX5C) - { - _ = Throws.IfNull(popPublicKey); - _ = Throws.IfNull(jwkClaim); - - builder.WithProofOfPosessionKeyId(popPublicKey); - builder.OnBeforeTokenRequest((data) => - { - string? signedAssertion = GetSignedClientAssertion( - clientCertificate, - data.RequestUri.AbsoluteUri, - jwkClaim, - clientId, - sendX5C); - - data.BodyParameters.Remove("client_assertion"); - data.BodyParameters.Add("request", signedAssertion); - - return Task.CompletedTask; - }); - - return builder; - } - - // Configure with Custom Signed Assertion - internal static AcquireTokenForClientParameterBuilder WithAtPop( - this AcquireTokenForClientParameterBuilder builder, - CredentialDescription credentialDescription, string popPublicKey, string jwkClaim) { - _ = Throws.IfNull(popPublicKey); - _ = Throws.IfNull(jwkClaim); + _ = Throws.IfNullOrWhitespace(popPublicKey); + _ = Throws.IfNullOrWhitespace(jwkClaim); - builder.WithProofOfPosessionKeyId(popPublicKey); - builder.OnBeforeTokenRequest((data) => + AtPopOperation op = new AtPopOperation(popPublicKey, jwkClaim); + builder.WithAuthenticationExtension(new MsalAuthenticationExtension() { - string? signedAssertion = credentialDescription.CachedValue as string; - data.BodyParameters["client_assertion"] = signedAssertion; - data.BodyParameters.Add("req_cnf", Base64UrlEncoder.Encode(jwkClaim)); - data.BodyParameters.Add("token_type", "pop"); - - return Task.CompletedTask; + AuthenticationOperation = op }); - return builder; } - - private static string? GetSignedClientAssertion( - X509Certificate2 certificate, - string audience, - string jwkClaim, - string clientId, - bool sendX5C) - { - // no need to add exp, nbf as JsonWebTokenHandler will add them by default - var claims = new Dictionary() - { - { "aud", audience }, - { "iss", clientId }, - { "jti", Guid.NewGuid().ToString() }, - { "sub", clientId }, - { "pop_jwk", jwkClaim } - }; - - var signingCredentials = new X509SigningCredentials(certificate); - var securityTokenDescriptor = new SecurityTokenDescriptor - { - Claims = claims, - SigningCredentials = signingCredentials - }; - - if (sendX5C) - { - string x5cValue = Convert.ToBase64String(certificate.GetRawCertData()); - securityTokenDescriptor.AdditionalHeaderClaims = - new Dictionary() { { "x5c", new List { x5cValue } } }; - } - - JsonWebTokenHandler tokenHandler = new JsonWebTokenHandler(); - string token = tokenHandler.CreateToken(securityTokenDescriptor); - - return token; - } } } diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/InternalAPI.Unshipped.txt index e69de29bb..9d7f2e5ab 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/InternalAPI.Unshipped.txt @@ -0,0 +1,9 @@ +Microsoft.Identity.Web.AtPopOperation +Microsoft.Identity.Web.AtPopOperation.AccessTokenType.get -> string! +Microsoft.Identity.Web.AtPopOperation.AtPopOperation(string! keyId, string! reqCnf) -> void +Microsoft.Identity.Web.AtPopOperation.AuthorizationHeaderPrefix.get -> string! +Microsoft.Identity.Web.AtPopOperation.FormatResult(Microsoft.Identity.Client.AuthenticationResult! authenticationResult) -> void +Microsoft.Identity.Web.AtPopOperation.GetTokenRequestParams() -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.Identity.Web.AtPopOperation.KeyId.get -> string! +Microsoft.Identity.Web.AtPopOperation.TelemetryTokenType.get -> int +static Microsoft.Identity.Web.MsAuth10AtPop.WithAtPop(this Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, string! popPublicKey, string! jwkClaim) -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/InternalAPI.Unshipped.txt index e69de29bb..9d7f2e5ab 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/InternalAPI.Unshipped.txt @@ -0,0 +1,9 @@ +Microsoft.Identity.Web.AtPopOperation +Microsoft.Identity.Web.AtPopOperation.AccessTokenType.get -> string! +Microsoft.Identity.Web.AtPopOperation.AtPopOperation(string! keyId, string! reqCnf) -> void +Microsoft.Identity.Web.AtPopOperation.AuthorizationHeaderPrefix.get -> string! +Microsoft.Identity.Web.AtPopOperation.FormatResult(Microsoft.Identity.Client.AuthenticationResult! authenticationResult) -> void +Microsoft.Identity.Web.AtPopOperation.GetTokenRequestParams() -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.Identity.Web.AtPopOperation.KeyId.get -> string! +Microsoft.Identity.Web.AtPopOperation.TelemetryTokenType.get -> int +static Microsoft.Identity.Web.MsAuth10AtPop.WithAtPop(this Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, string! popPublicKey, string! jwkClaim) -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/InternalAPI.Unshipped.txt index e69de29bb..9d7f2e5ab 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/InternalAPI.Unshipped.txt @@ -0,0 +1,9 @@ +Microsoft.Identity.Web.AtPopOperation +Microsoft.Identity.Web.AtPopOperation.AccessTokenType.get -> string! +Microsoft.Identity.Web.AtPopOperation.AtPopOperation(string! keyId, string! reqCnf) -> void +Microsoft.Identity.Web.AtPopOperation.AuthorizationHeaderPrefix.get -> string! +Microsoft.Identity.Web.AtPopOperation.FormatResult(Microsoft.Identity.Client.AuthenticationResult! authenticationResult) -> void +Microsoft.Identity.Web.AtPopOperation.GetTokenRequestParams() -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.Identity.Web.AtPopOperation.KeyId.get -> string! +Microsoft.Identity.Web.AtPopOperation.TelemetryTokenType.get -> int +static Microsoft.Identity.Web.MsAuth10AtPop.WithAtPop(this Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, string! popPublicKey, string! jwkClaim) -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Unshipped.txt index 8fd264ecc..9d7f2e5ab 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Unshipped.txt @@ -1 +1,9 @@ -static Microsoft.Identity.Web.MsAuth10AtPop.WithAtPop(this Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, Microsoft.Identity.Abstractions.CredentialDescription! credentialDescription, string! popPublicKey, string! jwkClaim) -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! \ No newline at end of file +Microsoft.Identity.Web.AtPopOperation +Microsoft.Identity.Web.AtPopOperation.AccessTokenType.get -> string! +Microsoft.Identity.Web.AtPopOperation.AtPopOperation(string! keyId, string! reqCnf) -> void +Microsoft.Identity.Web.AtPopOperation.AuthorizationHeaderPrefix.get -> string! +Microsoft.Identity.Web.AtPopOperation.FormatResult(Microsoft.Identity.Client.AuthenticationResult! authenticationResult) -> void +Microsoft.Identity.Web.AtPopOperation.GetTokenRequestParams() -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.Identity.Web.AtPopOperation.KeyId.get -> string! +Microsoft.Identity.Web.AtPopOperation.TelemetryTokenType.get -> int +static Microsoft.Identity.Web.MsAuth10AtPop.WithAtPop(this Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, string! popPublicKey, string! jwkClaim) -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Unshipped.txt index e69de29bb..9d7f2e5ab 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Unshipped.txt @@ -0,0 +1,9 @@ +Microsoft.Identity.Web.AtPopOperation +Microsoft.Identity.Web.AtPopOperation.AccessTokenType.get -> string! +Microsoft.Identity.Web.AtPopOperation.AtPopOperation(string! keyId, string! reqCnf) -> void +Microsoft.Identity.Web.AtPopOperation.AuthorizationHeaderPrefix.get -> string! +Microsoft.Identity.Web.AtPopOperation.FormatResult(Microsoft.Identity.Client.AuthenticationResult! authenticationResult) -> void +Microsoft.Identity.Web.AtPopOperation.GetTokenRequestParams() -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.Identity.Web.AtPopOperation.KeyId.get -> string! +Microsoft.Identity.Web.AtPopOperation.TelemetryTokenType.get -> int +static Microsoft.Identity.Web.MsAuth10AtPop.WithAtPop(this Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, string! popPublicKey, string! jwkClaim) -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Unshipped.txt index e69de29bb..9d7f2e5ab 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Unshipped.txt @@ -0,0 +1,9 @@ +Microsoft.Identity.Web.AtPopOperation +Microsoft.Identity.Web.AtPopOperation.AccessTokenType.get -> string! +Microsoft.Identity.Web.AtPopOperation.AtPopOperation(string! keyId, string! reqCnf) -> void +Microsoft.Identity.Web.AtPopOperation.AuthorizationHeaderPrefix.get -> string! +Microsoft.Identity.Web.AtPopOperation.FormatResult(Microsoft.Identity.Client.AuthenticationResult! authenticationResult) -> void +Microsoft.Identity.Web.AtPopOperation.GetTokenRequestParams() -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.Identity.Web.AtPopOperation.KeyId.get -> string! +Microsoft.Identity.Web.AtPopOperation.TelemetryTokenType.get -> int +static Microsoft.Identity.Web.MsAuth10AtPop.WithAtPop(this Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, string! popPublicKey, string! jwkClaim) -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt index e69de29bb..9d7f2e5ab 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt @@ -0,0 +1,9 @@ +Microsoft.Identity.Web.AtPopOperation +Microsoft.Identity.Web.AtPopOperation.AccessTokenType.get -> string! +Microsoft.Identity.Web.AtPopOperation.AtPopOperation(string! keyId, string! reqCnf) -> void +Microsoft.Identity.Web.AtPopOperation.AuthorizationHeaderPrefix.get -> string! +Microsoft.Identity.Web.AtPopOperation.FormatResult(Microsoft.Identity.Client.AuthenticationResult! authenticationResult) -> void +Microsoft.Identity.Web.AtPopOperation.GetTokenRequestParams() -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.Identity.Web.AtPopOperation.KeyId.get -> string! +Microsoft.Identity.Web.AtPopOperation.TelemetryTokenType.get -> int +static Microsoft.Identity.Web.MsAuth10AtPop.WithAtPop(this Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, string! popPublicKey, string! jwkClaim) -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs index 13ecb2a5b..340c5b922 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs @@ -588,12 +588,9 @@ public async Task GetAuthenticationResultForAppAsync( _logger.LogWarning("MSAuth POP configured with pinned certificate. This configuration is being deprecated."); } - ConfigureAtPopWithCredentials( - builder, - application.AppConfig, - tokenAcquisitionOptions, - mergedOptions.ClientCredentials, - mergedOptions.SendX5C); + builder.WithAtPop( + tokenAcquisitionOptions.PopPublicKey!, + tokenAcquisitionOptions.PopClaim!); } } } @@ -629,39 +626,6 @@ public async Task GetAuthenticationResultForAppAsync( } } - private static void ConfigureAtPopWithCredentials( - AcquireTokenForClientParameterBuilder builder, - IAppConfig appConfig, - TokenAcquisitionOptions tokenAcquisitionOptions, - IEnumerable? clientCredentials, - bool sendX5C) - { - // Try to configure AtPop with custom signed assertion first - if (clientCredentials != null) - { - foreach (var credential in clientCredentials) - { - if (credential.SourceType == CredentialSource.CustomSignedAssertion && - credential.CachedValue != null) - { - builder.WithAtPop( - credential, - tokenAcquisitionOptions.PopPublicKey!, - tokenAcquisitionOptions.PopClaim!); - return; - } - } - } - - // Fall back to certificate-based AtPop configuration - builder.WithAtPop( - appConfig.ClientCredentialCertificate, - tokenAcquisitionOptions.PopPublicKey!, - tokenAcquisitionOptions.PopClaim!, - appConfig.ClientId, - sendX5C); - } - /// /// Acquires a token from the authority configured in the app, for the confidential client itself (not on behalf of a user) /// using the client credentials flow. See https://aka.ms/msal-net-client-credentials. diff --git a/tests/Microsoft.Identity.Web.Test.Common/Mocks/MockHttpCreator.cs b/tests/Microsoft.Identity.Web.Test.Common/Mocks/MockHttpCreator.cs index e3759c223..19918c8d3 100644 --- a/tests/Microsoft.Identity.Web.Test.Common/Mocks/MockHttpCreator.cs +++ b/tests/Microsoft.Identity.Web.Test.Common/Mocks/MockHttpCreator.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using System.Collections; using System.Collections.Generic; using System.Net; using System.Net.Http; @@ -11,10 +10,13 @@ namespace Microsoft.Identity.Web.Test.Common.Mocks { public static class MockHttpCreator { - private static HttpResponseMessage CreateSuccessfulClientCredentialTokenResponseMessage(string token = "header.payload.signature", int expiry = 3599) + private static HttpResponseMessage CreateSuccessfulClientCredentialTokenResponseMessage( + string token = "header.payload.signature", + string tokenType = "Bearer", + int expiry = 3599) { return CreateSuccessResponseMessage( - "{\"token_type\":\"Bearer\",\"expires_in\":" + expiry + ",\"client_info\":\"" + CreateClientInfo() + "\",\"access_token\":\"" + token + "\"}"); + "{\"token_type\":\"" + tokenType + "\",\"expires_in\":" + expiry + ",\"client_info\":\"" + CreateClientInfo() + "\",\"access_token\":\"" + token + "\"}"); } public static HttpResponseMessage CreateSuccessResponseMessage(string successResponse) @@ -47,12 +49,12 @@ public static MockHttpMessageHandler CreateInstanceDiscoveryMockHandler( } public static MockHttpMessageHandler CreateClientCredentialTokenHandler( - string token = "header.payload.signature", int expiresIn = 3599) + string token = "header.payload.signature", string tokenType = "Bearer", int expiresIn = 3599) { var handler = new MockHttpMessageHandler() { ExpectedMethod = HttpMethod.Post, - ResponseMessage = CreateSuccessfulClientCredentialTokenResponseMessage(token, expiresIn), + ResponseMessage = CreateSuccessfulClientCredentialTokenResponseMessage(token, tokenType, expiresIn), }; return handler; diff --git a/tests/Microsoft.Identity.Web.Test/MsAuth10AtPopTests.cs b/tests/Microsoft.Identity.Web.Test/MsAuth10AtPopTests.cs index 5d1da2d0a..4649659d4 100644 --- a/tests/Microsoft.Identity.Web.Test/MsAuth10AtPopTests.cs +++ b/tests/Microsoft.Identity.Web.Test/MsAuth10AtPopTests.cs @@ -2,15 +2,13 @@ // Licensed under the MIT License. using System; -using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; -using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; -using Microsoft.Graph; using Microsoft.Identity.Client; using Microsoft.Identity.Web.Test.Common; using Microsoft.Identity.Web.Test.Common.Mocks; +using Microsoft.IdentityModel.Tokens; using Xunit; namespace Microsoft.Identity.Web.Test @@ -22,7 +20,7 @@ public async Task MsAuth10AtPop_WithAtPop_ShouldPopulateBuilderWithProofOfPosess { // Arrange using MockHttpClientFactory mockHttpClientFactory = new MockHttpClientFactory(); - using var httpTokenRequest = MockHttpCreator.CreateClientCredentialTokenHandler(); + using var httpTokenRequest = MockHttpCreator.CreateClientCredentialTokenHandler(tokenType: "pop"); mockHttpClientFactory.AddMockHandler(httpTokenRequest); var certificateDescription = CertificateDescription.FromBase64Encoded( @@ -44,15 +42,23 @@ public async Task MsAuth10AtPop_WithAtPop_ShouldPopulateBuilderWithProofOfPosess // Act AuthenticationResult result = await app.AcquireTokenForClient(new[] { TestConstants.Scopes }) - .WithAtPop(certificateDescription.Certificate, popPublicKey, jwkClaim, TestConstants.ClientId, true) + .WithAtPop(popPublicKey, jwkClaim) .ExecuteAsync(); // Assert httpTokenRequest.ActualRequestPostData.TryGetValue("request", out string? request); Assert.Null(request); + httpTokenRequest.ActualRequestPostData.TryGetValue("client_assertion", out string? clientAssertion); Assert.NotNull(clientAssertion); + // jwk is now passed in the http request as req_cnf + httpTokenRequest.ActualRequestPostData.TryGetValue("req_cnf", out string? reqCnf); + Assert.Equal(Base64UrlEncoder.Encode(jwkClaim), reqCnf); + + httpTokenRequest.ActualRequestPostData.TryGetValue("token_type", out string? tokenType); + Assert.Equal("pop", tokenType); + JwtSecurityTokenHandler jwtSecurityTokenHandler = new JwtSecurityTokenHandler(); JwtSecurityToken assertion = jwtSecurityTokenHandler.ReadJwtToken(clientAssertion); @@ -60,15 +66,9 @@ public async Task MsAuth10AtPop_WithAtPop_ShouldPopulateBuilderWithProofOfPosess Assert.Equal(TestConstants.ClientId, assertion.Claims.Single(c => c.Type == "iss").Value); Assert.Equal(TestConstants.ClientId, assertion.Claims.Single(c => c.Type == "sub").Value); Assert.NotEmpty(assertion.Claims.Single(c => c.Type == "jti").Value); - Assert.Equal(jwkClaim, assertion.Claims.Single(c => c.Type == "pop_jwk").Value); - - assertion.Header.TryGetValue("x5c", out var x5cClaimValue); - Assert.NotNull(x5cClaimValue); - string actualX5c = (string)((List)x5cClaimValue).Single(); - string expectedX5C= Convert.ToBase64String(certificateDescription.Certificate.RawData); - - Assert.Equal(expectedX5C, actualX5c); + // clientAssertion will no longer contain jwk + Assert.Null(assertion.Claims.SingleOrDefault(c => c.Type == "pop_jwk")); } [Fact] @@ -76,20 +76,13 @@ public void MsAuth10AtPop_ThrowsWithNullPopKeyTest() { // Arrange IConfidentialClientApplication app = CreateBuilder(); -#pragma warning disable SYSLIB0057 // Type or member is obsolete - using X509Certificate2 clientCertificate = new([]); -#pragma warning restore SYSLIB0057 // Type or member is obsolete var jwkClaim = "jwk_claim"; - var clientId = "client_id"; // Act & Assert - Assert.Throws(() => MsAuth10AtPop.WithAtPop( - app.AcquireTokenForClient(new[] { TestConstants.Scopes }), - clientCertificate, + Assert.Throws(() => MsAuth10AtPop.WithAtPop( + app.AcquireTokenForClient([TestConstants.Scopes]), string.Empty, - jwkClaim, - clientId, - true)); + jwkClaim)); } [Fact] @@ -97,17 +90,13 @@ public void MsAuth10AtPop_ThrowsWithNullJwkClaimTest() { // Arrange IConfidentialClientApplication app = CreateBuilder(); -#pragma warning disable SYSLIB0057 // Type or member is obsolete - using X509Certificate2 clientCertificate = new([]); -#pragma warning restore SYSLIB0057 // Type or member is obsolete var popPublicKey = "pop_key"; - var clientId = "client_id"; // Act & Assert #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. Assert.Throws(() => MsAuth10AtPop.WithAtPop( app.AcquireTokenForClient(new[] { TestConstants.Scopes }), - clientCertificate, popPublicKey, null, clientId, true)); + popPublicKey, null)); #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. } From d380e0875e28338818b4371181626b921af00a93 Mon Sep 17 00:00:00 2001 From: "Sruthi Keerthi Rangavajhula (from Dev Box)" Date: Mon, 24 Mar 2025 14:10:06 -0700 Subject: [PATCH 4/9] Adds AtPopOperationTests --- .../AtPopOperationTests.cs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/Microsoft.Identity.Web.Test/AtPopOperationTests.cs diff --git a/tests/Microsoft.Identity.Web.Test/AtPopOperationTests.cs b/tests/Microsoft.Identity.Web.Test/AtPopOperationTests.cs new file mode 100644 index 000000000..16796361e --- /dev/null +++ b/tests/Microsoft.Identity.Web.Test/AtPopOperationTests.cs @@ -0,0 +1,44 @@ +using Microsoft.Identity.Client; +using Microsoft.Identity.Web; +using Microsoft.IdentityModel.Tokens; +using System.Collections.Generic; +using Xunit; + +namespace Microsoft.Identity.Web.TokenAcquisition.Tests +{ + public class AtPopOperationTests + { + [Fact] + public void Constructor_InitializesProperties() + { + // Arrange + string keyId = "testKeyId"; + string reqCnf = "testReqCnf"; + + // Act + var atPopOperation = new AtPopOperation(keyId, reqCnf); + + // Assert + Assert.Equal(keyId, atPopOperation.KeyId); + Assert.Equal(4, atPopOperation.TelemetryTokenType); + Assert.Equal("Bearer", atPopOperation.AuthorizationHeaderPrefix); + Assert.Equal("pop", atPopOperation.AccessTokenType); + } + + [Fact] + public void GetTokenRequestParams_ReturnsCorrectDictionary() + { + // Arrange + string reqCnf = "testReqCnf"; + var atPopOperation = new AtPopOperation("testKeyId", reqCnf); + + // Act + var tokenRequestParams = atPopOperation.GetTokenRequestParams(); + + // Assert + Assert.Equal(2, tokenRequestParams.Count); + Assert.Equal(Base64UrlEncoder.Encode(reqCnf), tokenRequestParams["req_cnf"]); + Assert.Equal("pop", tokenRequestParams["token_type"]); + } + } +} From d1b0021745bbf5e0ae6a13990a4232a54a5bba4f Mon Sep 17 00:00:00 2001 From: Kashif Mehmood <33881304+ksaaf@users.noreply.github.com> Date: Fri, 14 Mar 2025 16:50:15 -0700 Subject: [PATCH 5/9] Make Microsoft.Extensions.Http dependency framework friendly (#3296) This PR pins Microsoft.Extensions.Http ver 3.1.3 to .Net Framework/Standard and uses inbox version of Microsoft.Extensions.Http for .Net Core. Change involves updating Directory.Build.props and updating CSProj to reference Microsoft.Extensions.Http package only for non .Net Core. --------- Co-authored-by: Kashif Mehmood --- Directory.Build.props | 3 ++- .../Microsoft.Identity.Web.OWIN.csproj | 5 ++++- .../Microsoft.Identity.Web.TokenAcquisition.csproj | 5 ++++- src/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj | 7 +++++-- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 14db851ce..3d6eb76e0 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -95,7 +95,6 @@ 4.6.0 4.36.0 4.57.0-preview - 3.1.3 8.2.0 8.0.5 @@ -173,6 +172,7 @@ 6.0.2 6.0.0 + 3.1.3 6.0.0 7.0.2 6.0.1 @@ -196,6 +196,7 @@ 4.7.1 2.1.0 2.1.1 + 3.1.3 2.1.0 2.1.0 2.2.4 diff --git a/src/Microsoft.Identity.Web.OWIN/Microsoft.Identity.Web.OWIN.csproj b/src/Microsoft.Identity.Web.OWIN/Microsoft.Identity.Web.OWIN.csproj index 25dfd23c7..612cff1d7 100644 --- a/src/Microsoft.Identity.Web.OWIN/Microsoft.Identity.Web.OWIN.csproj +++ b/src/Microsoft.Identity.Web.OWIN/Microsoft.Identity.Web.OWIN.csproj @@ -29,7 +29,6 @@ - @@ -40,6 +39,10 @@ + + + + diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/Microsoft.Identity.Web.TokenAcquisition.csproj b/src/Microsoft.Identity.Web.TokenAcquisition/Microsoft.Identity.Web.TokenAcquisition.csproj index 1a5956163..51b1aa9e7 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/Microsoft.Identity.Web.TokenAcquisition.csproj +++ b/src/Microsoft.Identity.Web.TokenAcquisition/Microsoft.Identity.Web.TokenAcquisition.csproj @@ -35,10 +35,13 @@ - + + + + diff --git a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj index 0642d4726..7408da953 100644 --- a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj +++ b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj @@ -25,11 +25,14 @@ - - + + + + + From 3438224bb448e04534117d71d57e21bd37ee27d8 Mon Sep 17 00:00:00 2001 From: Peter <34331512+pmaytak@users.noreply.github.com> Date: Fri, 21 Mar 2025 10:14:39 -0700 Subject: [PATCH 6/9] Update to IdentityModel 8.7.0 (#3307) --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 3d6eb76e0..f536206b3 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -86,7 +86,7 @@ - 8.6.1 + 8.7.0 4.69.1 3.3.0 4.7.2 From 76a36a083d5ab263aa8c59c7e91dd8f11e7350b0 Mon Sep 17 00:00:00 2001 From: Peter <34331512+pmaytak@users.noreply.github.com> Date: Fri, 21 Mar 2025 10:38:18 -0700 Subject: [PATCH 7/9] 3.8.1 changelog (#3306) --- changelog.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/changelog.md b/changelog.md index a15db3ae8..7483ede34 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,11 @@ +3.8.1 +======== +### New features +- Updated to Microsoft.IdentityModel.* [8.7.0](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases/tag/8.7.0) + +### Bug fixes +- Pins Microsoft.Extensions.Http dependency version to 3.1.3 for .NET Framework and .NET Standard and uses inbox version for .NET Core. See [#3145](https://github.com/AzureAD/microsoft-identity-web/issues/3145). + 3.8.0 ======== ### New feature From 2d319cbf35a1e6b5b759b1f683a1bdf163cb1330 Mon Sep 17 00:00:00 2001 From: Travis Walker Date: Fri, 21 Mar 2025 11:15:21 -0700 Subject: [PATCH 8/9] Update Directory.Build.props (#3305) --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index f536206b3..44087f21a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -87,7 +87,7 @@ 8.7.0 - 4.69.1 + 4.70.0 3.3.0 4.7.2 4.6.0 From d7e29d3dcf3f97e0728c583025b19720c43bb76d Mon Sep 17 00:00:00 2001 From: sruthikeerthi <73967733+sruke@users.noreply.github.com> Date: Mon, 24 Mar 2025 12:23:58 -0700 Subject: [PATCH 9/9] Update src/Microsoft.Identity.Web.TokenAcquisition/AtPopOperation.cs Co-authored-by: jennyf19 --- .../AtPopOperation.cs | 1 + tests/Microsoft.Identity.Web.Test/AtPopOperationTests.cs | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/AtPopOperation.cs b/src/Microsoft.Identity.Web.TokenAcquisition/AtPopOperation.cs index 68627e1f9..bfa4a7a11 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/AtPopOperation.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/AtPopOperation.cs @@ -17,6 +17,7 @@ public AtPopOperation(string keyId, string reqCnf) KeyId = keyId; _reqCnf = reqCnf; } + public int TelemetryTokenType => 4; // as per TelemetryTokenTypeConstants public string AuthorizationHeaderPrefix => "Bearer"; // these tokens go over bearer diff --git a/tests/Microsoft.Identity.Web.Test/AtPopOperationTests.cs b/tests/Microsoft.Identity.Web.Test/AtPopOperationTests.cs index 16796361e..3f9c17aea 100644 --- a/tests/Microsoft.Identity.Web.Test/AtPopOperationTests.cs +++ b/tests/Microsoft.Identity.Web.Test/AtPopOperationTests.cs @@ -1,10 +1,10 @@ -using Microsoft.Identity.Client; -using Microsoft.Identity.Web; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + using Microsoft.IdentityModel.Tokens; -using System.Collections.Generic; using Xunit; -namespace Microsoft.Identity.Web.TokenAcquisition.Tests +namespace Microsoft.Identity.Web.Test { public class AtPopOperationTests {