Skip to content

Commit 1451efb

Browse files
authored
MsAuth10AtPop part2 (#2109)
* initial commit for MsAuth10AtPop * fix pop and add test * Update src/Microsoft.Identity.Web.TokenAcquisition/MsAuth10AtPop.cs
1 parent 5b63675 commit 1451efb

7 files changed

Lines changed: 161 additions & 14 deletions

File tree

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
<MicrosoftGraphVersion>4.34.0</MicrosoftGraphVersion>
7070
<MicrosoftGraphBetaVersion>4.50.0-preview</MicrosoftGraphBetaVersion>
7171
<MicrosoftExtensionsHttpVersion>3.1.3</MicrosoftExtensionsHttpVersion>
72-
<MicrosoftIdentityAbstractions>2.0.1</MicrosoftIdentityAbstractions>
72+
<MicrosoftIdentityAbstractions>2.1.0</MicrosoftIdentityAbstractions>
7373
<!--CVE-2021-24112-->
7474
<SystemDrawingCommon>4.7.2</SystemDrawingCommon>
7575
</PropertyGroup>

Microsoft.Identity.Web.sln

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,6 @@ EndProject
5353
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerformanceTestService", "tests\PerformanceTests\PerformanceTestService\PerformanceTestService.csproj", "{AA1F64B8-EFA8-4000-9B91-A8B0D50A5D0E}"
5454
EndProject
5555
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IntegrationTests", "IntegrationTests", "{A7B1AE31-4E89-42A0-8264-FBEA795AB7D2}"
56-
ProjectSection(SolutionItems) = preProject
57-
tests\IntegrationTests\Directory.Build.props = tests\IntegrationTests\Directory.Build.props
58-
tests\IntegrationTests\TokenAcquirerTests\TokenAcquirerTests.csproj = tests\IntegrationTests\TokenAcquirerTests\TokenAcquirerTests.csproj
59-
EndProjectSection
6056
EndProject
6157
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebAppUiTests", "tests\IntegrationTests\WebAppUiTests\WebAppUiTests.csproj", "{ECDE3310-9548-491F-A0C6-8F086E0579D3}"
6258
EndProject
@@ -166,6 +162,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MinimalWebApi", "tests\DevA
166162
EndProject
167163
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DaemonConsoleCallingDownstreamApi", "tests\DevApps\daemon-app\daemon-console-calling-downstreamApi\DaemonConsoleCallingDownstreamApi.csproj", "{5DE68949-118E-4A2E-A541-8FB5CA030CD6}"
168164
EndProject
165+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TokenAcquirerTests", "tests\IntegrationTests\TokenAcquirerTests\TokenAcquirerTests.csproj", "{AB8177BE-8961-4698-B064-42943885D48D}"
166+
EndProject
169167
Global
170168
GlobalSection(SolutionConfigurationPlatforms) = preSolution
171169
Debug|Any CPU = Debug|Any CPU
@@ -371,6 +369,10 @@ Global
371369
{5DE68949-118E-4A2E-A541-8FB5CA030CD6}.Debug|Any CPU.Build.0 = Debug|Any CPU
372370
{5DE68949-118E-4A2E-A541-8FB5CA030CD6}.Release|Any CPU.ActiveCfg = Release|Any CPU
373371
{5DE68949-118E-4A2E-A541-8FB5CA030CD6}.Release|Any CPU.Build.0 = Release|Any CPU
372+
{AB8177BE-8961-4698-B064-42943885D48D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
373+
{AB8177BE-8961-4698-B064-42943885D48D}.Debug|Any CPU.Build.0 = Debug|Any CPU
374+
{AB8177BE-8961-4698-B064-42943885D48D}.Release|Any CPU.ActiveCfg = Release|Any CPU
375+
{AB8177BE-8961-4698-B064-42943885D48D}.Release|Any CPU.Build.0 = Release|Any CPU
374376
EndGlobalSection
375377
GlobalSection(SolutionProperties) = preSolution
376378
HideSolutionNode = FALSE
@@ -445,6 +447,7 @@ Global
445447
{30CFD8F7-8B85-4433-8EDB-A644FCE28804} = {E37CDBC1-18F6-4C06-A3EE-532C9106721F}
446448
{D12AF43A-72EC-4459-B6F4-0755190D9222} = {E37CDBC1-18F6-4C06-A3EE-532C9106721F}
447449
{5DE68949-118E-4A2E-A541-8FB5CA030CD6} = {E37CDBC1-18F6-4C06-A3EE-532C9106721F}
450+
{AB8177BE-8961-4698-B064-42943885D48D} = {A7B1AE31-4E89-42A0-8264-FBEA795AB7D2}
448451
EndGlobalSection
449452
GlobalSection(ExtensibilityGlobals) = postSolution
450453
SolutionGuid = {104367F1-CE75-4F40-B32F-F14853973187}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Security.Cryptography.X509Certificates;
7+
using System.Threading.Tasks;
8+
using Microsoft.Identity.Client;
9+
using Microsoft.Identity.Client.Extensibility;
10+
using Microsoft.IdentityModel.JsonWebTokens;
11+
using Microsoft.IdentityModel.Tokens;
12+
13+
namespace Microsoft.Identity.Web
14+
{
15+
internal static class MsAuth10AtPop
16+
{
17+
public static AcquireTokenForClientParameterBuilder WithAtPop(
18+
this AcquireTokenForClientParameterBuilder builder,
19+
X509Certificate2 clientCertificate,
20+
string popPublicKey,
21+
string jwkClaim,
22+
string clientId)
23+
{
24+
builder.WithProofOfPosessionKeyId(popPublicKey);
25+
builder.OnBeforeTokenRequest((data) =>
26+
{
27+
string? signedAssertion = GetSignedClientAssertion(
28+
clientCertificate,
29+
data.RequestUri.AbsoluteUri,
30+
jwkClaim,
31+
clientId);
32+
33+
data.BodyParameters.Remove("client_assertion");
34+
data.BodyParameters.Add("request", signedAssertion);
35+
return Task.CompletedTask;
36+
});
37+
38+
return builder;
39+
}
40+
41+
private static string? GetSignedClientAssertion(
42+
X509Certificate2 certificate,
43+
string audience,
44+
string jwkClaim,
45+
string clientId)
46+
{
47+
// no need to add exp, nbf as JsonWebTokenHandler will add them by default
48+
var claims = new Dictionary<string, object>()
49+
{
50+
{ "aud", audience },
51+
{ "iss", clientId },
52+
{ "jti", Guid.NewGuid().ToString() },
53+
{ "sub", clientId },
54+
{ "pop_jwk", jwkClaim }
55+
};
56+
57+
var securityTokenDescriptor = new SecurityTokenDescriptor
58+
{
59+
Claims = claims,
60+
SigningCredentials = new X509SigningCredentials(certificate)
61+
};
62+
63+
var handler = new JsonWebTokenHandler();
64+
return handler.CreateToken(securityTokenDescriptor);
65+
}
66+
}
67+
}

src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquirer.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ async Task<AcquireTokenResult> ITokenAcquirer.GetTokenForAppAsync(string scope,
8282
Tenant = tokenAcquisitionOptions.Tenant,
8383
UserFlow = tokenAcquisitionOptions.UserFlow,
8484
PopPublicKey = tokenAcquisitionOptions.PopPublicKey,
85+
JwkClaim = tokenAcquisitionOptions.JwkClaim,
8586
};
8687
}
8788
}

src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -370,15 +370,26 @@ public Task<AuthenticationResult> GetAuthenticationResultForAppAsync(
370370
{
371371
builder.WithProofOfPossession(tokenAcquisitionOptions.PoPConfiguration);
372372
}
373-
if (tokenAcquisitionOptions.PopPublicKey != null)
373+
if (!string.IsNullOrEmpty(tokenAcquisitionOptions.PopPublicKey))
374374
{
375-
builder.WithProofOfPosessionKeyId(tokenAcquisitionOptions.PopPublicKey, "pop");
376-
builder.OnBeforeTokenRequest((data) =>
375+
if (string.IsNullOrEmpty(tokenAcquisitionOptions.JwkClaim))
377376
{
378-
data.BodyParameters.Add("req_cnf", tokenAcquisitionOptions.PopPublicKey);
379-
data.BodyParameters.Add("token_type", "pop");
380-
return Task.CompletedTask;
381-
});
377+
builder.WithProofOfPosessionKeyId(tokenAcquisitionOptions.PopPublicKey, "pop");
378+
builder.OnBeforeTokenRequest((data) =>
379+
{
380+
data.BodyParameters.Add("req_cnf", tokenAcquisitionOptions.PopPublicKey);
381+
data.BodyParameters.Add("token_type", "pop");
382+
return Task.CompletedTask;
383+
});
384+
}
385+
else
386+
{
387+
builder.WithAtPop(
388+
application.AppConfig.ClientCredentialCertificate,
389+
tokenAcquisitionOptions.PopPublicKey,
390+
tokenAcquisitionOptions.JwkClaim,
391+
application.AppConfig.ClientId);
392+
}
382393
}
383394
}
384395

src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisitionOptions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class TokenAcquisitionOptions : AcquireTokenOptions
4242
Claims = Claims,
4343
PoPConfiguration = PoPConfiguration,
4444
PopPublicKey = PopPublicKey,
45+
JwkClaim = JwkClaim,
4546
CancellationToken = CancellationToken,
4647
LongRunningWebApiSessionKey = LongRunningWebApiSessionKey,
4748
};

tests/IntegrationTests/TokenAcquirerTests/TokenAcquirer.cs

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

4+
using System;
45
using System.Linq;
6+
using System.Security.Cryptography;
57
using System.Security.Cryptography.X509Certificates;
68
using System.Threading.Tasks;
79
using Microsoft.Extensions.DependencyInjection;
@@ -144,6 +146,70 @@ public async Task AcquireTokenWithPop_ClientCredentialsAsync()
144146
Assert.NotNull(result.AccessToken);
145147
}
146148

149+
[IgnoreOnAzureDevopsFact]
150+
//[Fact]
151+
public async Task AcquireTokenWithMs10AtPop_ClientCredentialsAsync()
152+
{
153+
TokenAcquirerFactory tokenAcquirerFactory = TokenAcquirerFactory.GetDefaultInstance();
154+
IServiceCollection services = tokenAcquirerFactory.Services;
155+
156+
services.Configure<MicrosoftIdentityApplicationOptions>(s_optionName, option =>
157+
{
158+
option.Instance = "https://login.microsoftonline.com/";
159+
option.TenantId = "msidentitysamplestesting.onmicrosoft.com";
160+
option.ClientId = "6af093f3-b445-4b7a-beae-046864468ad6";
161+
option.ClientCredentials = s_clientCredentials;
162+
});
163+
164+
services.AddInMemoryTokenCaches();
165+
var serviceProvider = tokenAcquirerFactory.Build();
166+
var options = serviceProvider.GetRequiredService<IOptionsMonitor<MicrosoftIdentityApplicationOptions>>().Get(s_optionName);
167+
var credentialsLoader = serviceProvider.GetRequiredService<ICredentialsLoader>();
168+
await credentialsLoader.LoadCredentialsIfNeededAsync(options.ClientCredentials!.First());
169+
var cert = options.ClientCredentials!.First().Certificate;
170+
171+
// Get the token acquisition service
172+
ITokenAcquirer tokenAcquirer = tokenAcquirerFactory.GetTokenAcquirer(s_optionName);
173+
RsaSecurityKey rsaSecurityKey = CreateRsaSecurityKey();
174+
var result = await tokenAcquirer.GetTokenForAppAsync("https://graph.microsoft.com/.default",
175+
new TokenAcquisitionOptions()
176+
{
177+
PopPublicKey = rsaSecurityKey.KeyId,
178+
JwkClaim = CreateJwkClaim(rsaSecurityKey, SecurityAlgorithms.RsaSha256)
179+
});
180+
Assert.NotNull(result.AccessToken);
181+
}
182+
183+
private static string CreateJwkClaim(RsaSecurityKey key, string algorithm)
184+
{
185+
var parameters = key.Rsa == null ? key.Parameters : key.Rsa.ExportParameters(false);
186+
return "{\"kty\":\"RSA\",\"n\":\"" + Base64UrlEncoder.Encode(parameters.Modulus) + "\",\"e\":\"" + Base64UrlEncoder.Encode(parameters.Exponent) + "\",\"alg\":\"" + algorithm + "\",\"kid\":\"" + key.KeyId + "\"}";
187+
}
188+
189+
private static RsaSecurityKey CreateRsaSecurityKey()
190+
{
191+
#if NET472
192+
RSA rsa = RSA.Create(2048);
193+
#else
194+
RSA rsa = new RSACryptoServiceProvider(2048);
195+
#endif
196+
// the reason for creating the RsaSecurityKey from RSAParameters is so that a SignatureProvider created with this key
197+
// will own the RSA object and dispose it. If we pass a RSA object, the SignatureProvider does not own the object, the RSA object will not be disposed.
198+
RSAParameters rsaParameters = rsa.ExportParameters(true);
199+
RsaSecurityKey rsaSecuirtyKey = new RsaSecurityKey(rsaParameters) { KeyId = CreateRsaKeyId(rsaParameters) };
200+
rsa.Dispose();
201+
return rsaSecuirtyKey;
202+
}
203+
204+
private static string CreateRsaKeyId(RSAParameters rsaParameters)
205+
{
206+
byte[] kidBytes = new byte[rsaParameters.Exponent.Length + rsaParameters.Modulus.Length];
207+
Array.Copy(rsaParameters.Exponent, 0, kidBytes, 0, rsaParameters.Exponent.Length);
208+
Array.Copy(rsaParameters.Modulus, 0, kidBytes, rsaParameters.Exponent.Length, rsaParameters.Modulus.Length);
209+
using (var sha2 = SHA256.Create())
210+
return Base64UrlEncoder.Encode(sha2.ComputeHash(kidBytes));
211+
}
212+
147213
private string? ComputePublicKeyString(X509Certificate2? certificate)
148214
{
149215
if (certificate == null)
@@ -159,8 +225,6 @@ public async Task AcquireTokenWithPop_ClientCredentialsAsync()
159225
return keyId;
160226
}
161227

162-
163-
164228
private static async Task CreateGraphClientAndAssert(TokenAcquirerFactory tokenAcquirerFactory, IServiceCollection services)
165229
{
166230
services.AddInMemoryTokenCaches();

0 commit comments

Comments
 (0)