Skip to content

Commit 5302a67

Browse files
Add unit test to validate that the custom claim is string containing json (#5597)
1 parent a43ec82 commit 5302a67

File tree

1 file changed

+61
-6
lines changed

1 file changed

+61
-6
lines changed

tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,18 @@
1010
using System.Net.Http;
1111
using System.Security.Cryptography;
1212
using System.Security.Cryptography.X509Certificates;
13+
using System.Text.Json;
1314
using System.Threading;
1415
using System.Threading.Tasks;
1516
using Microsoft.Identity.Client;
16-
using Microsoft.Identity.Client.Cache.Keys;
1717
using Microsoft.Identity.Client.Internal;
18+
using Microsoft.Identity.Client.OAuth2;
1819
using Microsoft.Identity.Client.PlatformsCommon.Shared;
20+
using Microsoft.Identity.Client.RP;
1921
using Microsoft.Identity.Client.Utils;
2022
using Microsoft.Identity.Test.Common.Core.Helpers;
2123
using Microsoft.Identity.Test.Common.Core.Mocks;
2224
using Microsoft.VisualStudio.TestTools.UnitTesting;
23-
using static Microsoft.Identity.Client.Internal.JsonWebToken;
24-
using Microsoft.Identity.Client.RP;
25-
using Microsoft.Identity.Client.Http;
26-
using Microsoft.Identity.Client.OAuth2;
2725

2826
namespace Microsoft.Identity.Test.Unit
2927
{
@@ -648,7 +646,6 @@ public void ClientAssertionTests(bool sendX5C, bool useSha2AndPss, bool addExtra
648646
"MIIDQjCCAiqgAwIBAgIQTuexEO9cdYhC0jy1nmS6jTANBgkqhkiG9w0BAQsFADAiMSAwHgYDVQQDDBd0cndhbGtlLm9ubWljcm9zb2Z0LmNvbTAeFw0xNzA4MTExODEzMTBaFw0xODA4MTExODMzMTBaMCIxIDAeBgNVBAMMF3Ryd2Fsa2Uub25taWNyb3NvZnQuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5Qe3Ah/E97K0o288gYUNa0H8FO/w8pb1dvls/boQDoZxUD11TpAQrKZwstS6+ulGF6cHmj44AH8MNBKNUbW2L1NTjFG9bltaSXpJXzbIH/cUppF9rxngZ0CM7cHtuoccBPBVEuQiJ86pD7qlqE2EA2BdBmfz3Hd41rybdaWkHMxMcBC7nh6w87/KoyikKXCMLUUyRTJLSivo+gfKJsiYGAjqZ54aJraP5LMiPG2qYTOZR6wMme93mYRp85sqGTvgzRCq37STH2HmcYilUQ9kZFe5SR+1vOki97XLg+H7FuFtkSMM7dEnTWkDv+BJ1ZQvCEj623cJxXlq0fd7hVUxIQIDAQABo3QwcjAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMCIGA1UdEQQbMBmCF3Ryd2Fsa2Uub25taWNyb3NvZnQuY29tMB0GA1UdDgQWBBSauRo9cNk8J6RTLWMQSyUQnxjQzDANBgkqhkiG9w0BAQsFAAOCAQEAhYl1I8qETtvVt6m/YrGknA90R/FtIePt/ViBae3mxPJWlVoq5fTTriQcuPHXfI5kbjTQJIwCVTT/CRSlKkzRcrSsQUxxHNE7IdpvvDbkf6AMPxQhNACHQd0cIWmsmf+ItKsC70LKQ+93+VgmBsv2j8XwF0JTqwuKoqXnDjCzHvmU67xhPY6CSPA/0XOiVTx1BDWd5cPdsH2bZnAeApsvrzU8W7iPgV/oN9MMfogocvDUXd6T+QGLMAYoInHXsqG6+SEarqRDUPQZOHo5Ax4Mvhsnd2b4u5d5Y/R0z0wUwtOiF0Tu+w79JIqDRYaaJLTKxZ+2DyYOu54u0LGsGhki1g==",
649647
decodedToken.Header["x5c"]);
650648
}
651-
652649
}
653650

654651
private static void AssertClientAssertionHeader(
@@ -1057,6 +1054,64 @@ public async Task AcquireTokenForClient_ShouldSendClientInfoParameter_WithValueT
10571054
}
10581055
}
10591056

1057+
[TestMethod]
1058+
public async Task ClientAssertionWithComplexClaims()
1059+
{
1060+
using (var harness = CreateTestHarness())
1061+
{
1062+
harness.HttpManager.AddInstanceDiscoveryMockHandler();
1063+
var certificate = CertHelper.GetOrCreateTestCert();
1064+
var exportedCertificate = Convert.ToBase64String(certificate.Export(X509ContentType.Cert));
1065+
1066+
// Test complex nested claims as described in the issue
1067+
IDictionary<string, string> extraAssertionContent = new Dictionary<string, string>
1068+
{
1069+
{ "foo", "bar" },
1070+
{ "custom_claims", "{\"xms_foo\":[\"abc\",\"def\"],\"xms_az_foo\":\"bar\"}" }
1071+
};
1072+
1073+
var cca = ConfidentialClientApplicationBuilder
1074+
.Create(TestConstants.ClientId)
1075+
.WithAuthority("https://login.microsoftonline.com/tid")
1076+
.WithHttpManager(harness.HttpManager)
1077+
.WithClientClaims(certificate, extraAssertionContent, mergeWithDefaultClaims: true, sendX5C: true)
1078+
.Build();
1079+
1080+
var handler = harness.HttpManager.AddTokenResponse(TokenResponseType.Valid_ClientCredentials);
1081+
JwtSecurityToken assertion = null;
1082+
handler.AdditionalRequestValidation = (r) =>
1083+
{
1084+
var requestContent = r.Content.ReadAsStringAsync().GetAwaiter().GetResult();
1085+
var formsData = CoreHelpers.ParseKeyValueList(requestContent, '&', true, null);
1086+
1087+
// Check presence of client_assertion in request
1088+
Assert.IsTrue(formsData.TryGetValue("client_assertion", out string encodedJwt), "Missing client_assertion from request");
1089+
1090+
// Decode and validate the JWT
1091+
var jwtHandler = new JwtSecurityTokenHandler();
1092+
assertion = jwtHandler.ReadJwtToken(encodedJwt);
1093+
1094+
// Validate that custom_claims is a nested object, not an escaped string
1095+
var customClaimsClaim = assertion.Claims.FirstOrDefault(c => c.Type == "custom_claims");
1096+
Assert.IsNotNull(customClaimsClaim, "custom_claims should be present");
1097+
1098+
// Validate the type of claim value is a string
1099+
Assert.IsTrue(typeof(string).IsAssignableFrom(customClaimsClaim.Value.GetType()), "custom_claims claim value should be a string");
1100+
1101+
// The value should be a JSON object, not an escaped string
1102+
string customClaimsValue = customClaimsClaim.Value;
1103+
var jsonElement = JsonSerializer.Deserialize<JsonElement>(customClaimsValue); // This will throw if not valid JSON object
1104+
Assert.AreEqual(JsonValueKind.Object, jsonElement.ValueKind, "custom_claims claim value should be a JSON object");
1105+
};
1106+
1107+
AuthenticationResult result = await cca.AcquireTokenForClient(TestConstants.s_scope)
1108+
.ExecuteAsync()
1109+
.ConfigureAwait(false);
1110+
1111+
Assert.IsNotNull(result.AccessToken);
1112+
}
1113+
}
1114+
10601115
private void BeforeCacheAccess(TokenCacheNotificationArgs args)
10611116
{
10621117
args.TokenCache.DeserializeMsalV3(_serializedCache);

0 commit comments

Comments
 (0)