Skip to content

Commit 1e3737c

Browse files
authored
Add a test for msauth pop (#5205)
1 parent 5a8912b commit 1e3737c

File tree

1 file changed

+141
-0
lines changed

1 file changed

+141
-0
lines changed

tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/LegacyPopTest.NetFwk.cs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Text;
1010
using System.Threading.Tasks;
1111
using Microsoft.Identity.Client;
12+
using Microsoft.Identity.Client.AuthScheme;
1213
using Microsoft.Identity.Client.Cache.Items;
1314
using Microsoft.Identity.Client.Extensibility;
1415
using Microsoft.Identity.Test.Integration.Infrastructure;
@@ -20,6 +21,54 @@
2021

2122
namespace Microsoft.Identity.Test.Integration.HeadlessTests
2223
{
24+
internal static class MsAuth10AtPop
25+
{
26+
internal class MsAuth10AtPopOperation : IAuthenticationOperation
27+
{
28+
private readonly string _reqCnf;
29+
30+
public MsAuth10AtPopOperation(string keyId, string reqCnf)
31+
{
32+
KeyId = keyId;
33+
_reqCnf = reqCnf;
34+
}
35+
public int TelemetryTokenType => 4; // as per TelemetryTokenTypeConstants
36+
37+
public string AuthorizationHeaderPrefix => "Bearer"; // these tokens go over bearer
38+
39+
public string KeyId { get; }
40+
41+
public string AccessTokenType => "pop"; // eSTS returns token_type=pop and MSAL needs to know
42+
43+
public void FormatResult(AuthenticationResult authenticationResult)
44+
{
45+
// no-op, adding the SHR is done by the caller
46+
}
47+
48+
public IReadOnlyDictionary<string, string> GetTokenRequestParams()
49+
{
50+
return new Dictionary<string, string>()
51+
{
52+
{"req_cnf", Base64UrlEncoder.Encode(_reqCnf) },
53+
{"token_type", "pop" }
54+
};
55+
}
56+
}
57+
58+
internal static AcquireTokenForClientParameterBuilder WithAtPop(
59+
this AcquireTokenForClientParameterBuilder builder,
60+
string popPublicKey,
61+
string jwkClaim)
62+
{
63+
MsAuth10AtPopOperation op = new MsAuth10AtPopOperation(popPublicKey, jwkClaim);
64+
builder.WithAuthenticationExtension(new MsalAuthenticationExtension()
65+
{
66+
AuthenticationOperation = op
67+
});
68+
return builder;
69+
}
70+
}
71+
2372
// These tests run on .NET FWK as well. Use the RunOn attribute to limit this.
2473
[TestClass]
2574
public class LegacyPopTests
@@ -345,6 +394,84 @@ public async Task LegacyPoPAsync()
345394
Assert.IsNotNull(ats.SingleOrDefault(a => a.KeyId == popKey.KeyId));
346395
}
347396

397+
[TestMethod]
398+
public async Task LegacyPopUsingNewProtocol_CertThumbprinJWK_Async()
399+
{
400+
IConfidentialAppSettings settings = ConfidentialAppSettings.GetSettings(Cloud.Public);
401+
X509Certificate2 clientCredsCert = settings.GetCertificate();
402+
403+
var cca = ConfidentialClientApplicationBuilder
404+
.Create(settings.ClientId)
405+
.WithAuthority(settings.Authority, true)
406+
.WithCertificate(clientCredsCert)
407+
.WithExperimentalFeatures(true)
408+
.WithTestLogging()
409+
.Build();
410+
411+
var thumbprint = Base64UrlEncoder.Encode(clientCredsCert.GetCertHash(HashAlgorithmName.SHA256));
412+
var reqCnf = $@"{{""kty"":""RSA"",""x5t#S256"":""{thumbprint}"",""kid"":""{thumbprint}""}}";
413+
414+
var result = await cca.AcquireTokenForClient(settings.AppScopes)
415+
.WithAtPop(thumbprint, reqCnf)
416+
.ExecuteAsync().ConfigureAwait(false);
417+
418+
Assert.AreEqual(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource);
419+
MsalAccessTokenCacheItem at = (cca.AppTokenCache as ITokenCacheInternal).Accessor.GetAllAccessTokens().Single();
420+
Assert.AreEqual(at.KeyId, thumbprint);
421+
422+
result = await cca.AcquireTokenForClient(settings.AppScopes)
423+
.WithAtPop(thumbprint, reqCnf)
424+
.ExecuteAsync().ConfigureAwait(false);
425+
426+
Assert.AreEqual(TokenSource.Cache, result.AuthenticationResultMetadata.TokenSource);
427+
at = (cca.AppTokenCache as ITokenCacheInternal).Accessor.GetAllAccessTokens().Single();
428+
Assert.AreEqual(at.KeyId, thumbprint);
429+
430+
string otherKeyId = thumbprint + "2";
431+
result = await cca.AcquireTokenForClient(settings.AppScopes)
432+
.WithAtPop(otherKeyId, reqCnf)
433+
.ExecuteAsync().ConfigureAwait(false);
434+
435+
Assert.AreEqual(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource);
436+
var ats = (cca.AppTokenCache as ITokenCacheInternal).Accessor.GetAllAccessTokens();
437+
Assert.IsNotNull(ats.SingleOrDefault(a => a.KeyId == otherKeyId));
438+
Assert.IsNotNull(ats.SingleOrDefault(a => a.KeyId == thumbprint));
439+
}
440+
441+
[TestMethod]
442+
public async Task LegacyPopUsingNewProtocol_RsaKey_Async()
443+
{
444+
IConfidentialAppSettings settings = ConfidentialAppSettings.GetSettings(Cloud.Public);
445+
X509Certificate2 clientCredsCert = settings.GetCertificate();
446+
447+
var cca = ConfidentialClientApplicationBuilder
448+
.Create(settings.ClientId)
449+
.WithAuthority(settings.Authority, true)
450+
.WithCertificate(clientCredsCert)
451+
.WithExperimentalFeatures(true)
452+
.WithTestLogging()
453+
.Build();
454+
455+
RsaSecurityKey popKey = CreateRsaSecurityKey();
456+
var reqCnf = CreateJwkClaim(popKey, SecurityAlgorithms.RsaSha256);
457+
var keyId = ComputeSHA256(reqCnf); // can be anything
458+
459+
var result = await cca.AcquireTokenForClient(settings.AppScopes)
460+
.WithAtPop(keyId, reqCnf)
461+
.ExecuteAsync().ConfigureAwait(false);
462+
463+
Assert.AreEqual(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource);
464+
MsalAccessTokenCacheItem at = (cca.AppTokenCache as ITokenCacheInternal).Accessor.GetAllAccessTokens().Single();
465+
Assert.AreEqual(at.KeyId, keyId);
466+
467+
result = await cca.AcquireTokenForClient(settings.AppScopes)
468+
.WithAtPop(keyId, reqCnf)
469+
.ExecuteAsync().ConfigureAwait(false);
470+
471+
Assert.AreEqual(TokenSource.Cache, result.AuthenticationResultMetadata.TokenSource);
472+
473+
}
474+
348475
private static void ModifyRequestWithLegacyPop(OnBeforeTokenRequestData data, IConfidentialAppSettings settings, X509Certificate2 clientCredsCert, RsaSecurityKey popKey)
349476
{
350477
var clientCredsSigningCredentials = new SigningCredentials(new X509SecurityKey(clientCredsCert), SecurityAlgorithms.RsaSha256, SecurityAlgorithms.Sha256);
@@ -449,6 +576,20 @@ private static string CreateJwkClaim(RsaSecurityKey key, string algorithm)
449576
var parameters = key.Rsa == null ? key.Parameters : key.Rsa.ExportParameters(false);
450577
return "{\"kty\":\"RSA\",\"n\":\"" + Base64UrlEncoder.Encode(parameters.Modulus) + "\",\"e\":\"" + Base64UrlEncoder.Encode(parameters.Exponent) + "\",\"alg\":\"" + algorithm + "\",\"kid\":\"" + key.KeyId + "\"}";
451578
}
579+
580+
private static string ComputeSHA256(string token)
581+
{
582+
#if NET6_0_OR_GREATER
583+
byte[] hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(token));
584+
return Convert.ToBase64String(hashBytes);
585+
#else
586+
using (var sha256 = SHA256.Create())
587+
{
588+
byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(token));
589+
return Convert.ToBase64String(hashBytes);
590+
}
591+
#endif
592+
}
452593
}
453594

454595
}

0 commit comments

Comments
 (0)