Skip to content

Commit 0e5923e

Browse files
committed
2 parents b15a45b + 2424f26 commit 0e5923e

File tree

6 files changed

+158
-4
lines changed

6 files changed

+158
-4
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,21 @@ You also need to copy it from the server given claims to the local claims. E.g.
221221
yourContext.StoreRemoteAuthInSchemeAsync(..., (identity, remote)=>OidcClaimsCultureProviderHelper.CopyClaims(identity, remote))))
222222
```
223223

224+
## Claims helpers
225+
226+
https://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter specifies how to request claims from the request.
227+
228+
Notice, you still have to manually check they are present in the response.
229+
230+
```csharp
231+
//in the login controller
232+
var claims = new RequestClaimsParameterValue()
233+
.IdTokenClaim(Claims.AuthenticationTime, true);
234+
this.InitiateAuthorizationCodeLogin(returnUrl, ..., claims.AsOpenIddictParameter());
235+
...
236+
//in the callback controller
237+
this.StoreRemoteAuthInSchemeAsync(..., (principal)=>principal.GetClaim(Claims.AuthenticationTime)...)
238+
```
224239

225240
## Http helpers
226241

src/Catglobe.Openiddict.Contrib.Client/Catglobe.Openiddict.Contrib.Client.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<FrameworkReference Include="Microsoft.AspNetCore.App" />
1919
</ItemGroup>
2020

21-
<ItemGroup Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionLessThan($(TargetFrameworkVersion), '3.0'))) Or&#xD;&#xA; ('$(TargetFrameworkIdentifier)' == '.NETFramework') Or&#xD;&#xA; ('$(TargetFrameworkIdentifier)' == '.NETStandard') ">
21+
<ItemGroup Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionLessThan($(TargetFrameworkVersion), '3.0'))) Or ('$(TargetFrameworkIdentifier)' == '.NETFramework') Or ('$(TargetFrameworkIdentifier)' == '.NETStandard') ">
2222
<PackageReference Include="Microsoft.AspNetCore.Authentication" />
2323
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.Abstractions" />
2424
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" />

src/Catglobe.Openiddict.Contrib.Client/ControllerHelpers/AuthorizationCodeHelpers.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using OpenIddict.Client.AspNetCore;
33
using System.Security.Claims;
44
using Microsoft.AspNetCore.Mvc;
5+
using static OpenIddict.Abstractions.OpenIddictConstants;
56

67
namespace Openiddict.Contrib.Client.ControllerHelpers;
78

@@ -22,15 +23,18 @@ public static class AuthorizationCodeHelpers
2223
/// <param name="controller">The controller</param>
2324
/// <param name="returnUrl">Parameter from UI where the user will be redirected after the auth is done</param>
2425
/// <param name="provider">The client you want to authenticate with</param>
26+
/// <param name="claims">Any additional claims you want to request. See <see cref="RequestClaimsParameterValue.AsOpenIddictParameter"></see>.</param>
2527
/// <returns>The result you need to return from the controller</returns>
26-
public static ChallengeResult InitiateAuthorizationCodeLogin(this ControllerBase controller, string returnUrl, string? provider = null)
28+
public static ChallengeResult InitiateAuthorizationCodeLogin(this ControllerBase controller, string returnUrl, string? provider = null, OpenIddictParameter? claims = default)
2729
{
2830
var properties = new AuthenticationProperties {
2931
// Only allow local return URLs to prevent open redirect attacks.
3032
RedirectUri = controller.Url.IsLocalUrl(returnUrl) ? returnUrl : "/",
3133
};
3234
if (!string.IsNullOrEmpty(provider))
3335
properties.Items[OpenIddictClientAspNetCoreConstants.Properties.ProviderName] = provider;
36+
if (claims is {} claim)
37+
properties.Parameters[Parameters.Claims] = claim;
3438
// Ask the OpenIddict client middleware to redirect the user agent to the identity provider.
3539
return controller.Challenge(properties, OpenIddictClientAspNetCoreDefaults.AuthenticationScheme);
3640
}

src/Catglobe.Openiddict.Contrib.Client/MinimalApiHelpers/AuthorizationCodeHelpers.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,18 @@ public static class AuthorizationCodeHelpers
2121
/// <param name="httpContext">The controller</param>
2222
/// <param name="returnUrl">Parameter from UI where the user will be redirected after the auth is done</param>
2323
/// <param name="provider">The client you want to authenticate with</param>
24+
/// <param name="claims">Any additional claims you want to request. See <see cref="RequestClaimsParameterValue.AsOpenIddictParameter"></see>.</param>
2425
/// <returns>The result you need to return from the controller</returns>
25-
public static IResult InitiateAuthorizationCodeLogin(this HttpContext httpContext, string returnUrl, string? provider = null)
26+
public static IResult InitiateAuthorizationCodeLogin(this HttpContext httpContext, string returnUrl, string? provider = null, OpenIddictParameter? claims = default)
2627
{
2728
var properties = new AuthenticationProperties {
2829
// Only allow local return URLs to prevent open redirect attacks.
2930
RedirectUri = returnUrl,
3031
};
3132
if (!string.IsNullOrEmpty(provider))
3233
properties.Items[OpenIddictClientAspNetCoreConstants.Properties.ProviderName] = provider;
34+
if (claims is {} claim)
35+
properties.Parameters[Parameters.Claims] = claim;
3336
// Ask the OpenIddict client middleware to redirect the user agent to the identity provider.
3437
return Results.Challenge(properties, new List<string> { OpenIddictClientAspNetCoreDefaults.AuthenticationScheme });
3538
}

src/Catglobe.Openiddict.Contrib.Client/RazorPageHelpers/AuthorizationCodeHelpers.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,18 @@ public static class AuthorizationCodeHelpers
2323
/// <param name="model">The PageModel</param>
2424
/// <param name="returnUrl">Parameter from UI where the user will be redirected after the auth is done</param>
2525
/// <param name="provider">The client you want to authenticate with</param>
26+
/// <param name="claims">Any additional claims you want to request. See <see cref="RequestClaimsParameterValue.AsOpenIddictParameter"></see>.</param>
2627
/// <returns>The result you need to return from the PageModel</returns>
27-
public static ChallengeResult InitiateAuthorizationCodeLogin(this PageModel model, string returnUrl, string? provider = null)
28+
public static ChallengeResult InitiateAuthorizationCodeLogin(this PageModel model, string returnUrl, string? provider = null, OpenIddictParameter? claims = default)
2829
{
2930
var properties = new AuthenticationProperties {
3031
// Only allow local return URLs to prevent open redirect attacks.
3132
RedirectUri = model.Url.IsLocalUrl(returnUrl) ? returnUrl : "/",
3233
};
3334
if (!string.IsNullOrEmpty(provider))
3435
properties.Items[OpenIddictClientAspNetCoreConstants.Properties.ProviderName] = provider;
36+
if (claims is {} claim)
37+
properties.Parameters[Parameters.Claims] = claim;
3538
// Ask the OpenIddict client middleware to redirect the user agent to the identity provider.
3639
return model.Challenge(properties, OpenIddictClientAspNetCoreDefaults.AuthenticationScheme);
3740
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#if NET
2+
using System.Text.Json.Nodes;
3+
4+
namespace Openiddict.Contrib.Client;
5+
6+
/// <summary>
7+
/// Helper class for setting parameters matching https://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter.
8+
/// </summary>
9+
public class RequestClaimsParameterValue
10+
{
11+
private readonly Dictionary<string, JsonNode?> _userinfo = new();
12+
private readonly Dictionary<string, JsonNode?> _claims = new();
13+
14+
/// <summary>
15+
/// Specify a user info claim that should be returned from the authentication request.
16+
/// </summary>
17+
/// <param name="claim">See <see cref="Claims"/>.</param>
18+
/// <param name="essential">Indicates whether the Claim being requested is an Essential Claim. If the value is true, this indicates that the Claim is an Essential Claim.</param>
19+
/// <returns>This class for chaining.</returns>
20+
public RequestClaimsParameterValue UserInfoClaim(string claim, bool essential = false)
21+
{
22+
_userinfo.Add(claim, AsNode(essential, null, null));
23+
return this;
24+
}
25+
26+
/// <summary>
27+
/// Specify a user info claim that should be returned from the authentication request.
28+
/// </summary>
29+
/// <param name="claim">See <see cref="Claims"/>.</param>
30+
/// <param name="value">Requests that the Claim be returned with a particular value.</param>
31+
/// <param name="essential">Indicates whether the Claim being requested is an Essential Claim. If the value is true, this indicates that the Claim is an Essential Claim.</param>
32+
/// <returns>This class for chaining.</returns>
33+
public RequestClaimsParameterValue UserInfoClaim(string claim, string value, bool essential = false)
34+
{
35+
_userinfo.Add(claim, AsNode(essential, value, null));
36+
return this;
37+
}
38+
39+
/// <summary>
40+
/// Specify a user info claim that should be returned from the authentication request.
41+
/// </summary>
42+
/// <param name="claim">See <see cref="Claims"/>.</param>
43+
/// <param name="values">Requests that the Claim be returned with one of a set of values, with the values appearing in order of preference.</param>
44+
/// <param name="essential">Indicates whether the Claim being requested is an Essential Claim. If the value is true, this indicates that the Claim is an Essential Claim.</param>
45+
/// <returns>This class for chaining.</returns>
46+
public RequestClaimsParameterValue UserInfoClaim(string claim, IReadOnlyCollection<string> values, bool essential)
47+
{
48+
_userinfo.Add(claim, AsNode(essential, null, values));
49+
return this;
50+
}
51+
52+
private static JsonObject? AsNode(bool essential, string? value, IReadOnlyCollection<string?>? values)
53+
{
54+
var json = new JsonObject(GetJsonNode(essential, value, values));
55+
return json.Count == 0 ? default : json;
56+
}
57+
58+
/// <summary>
59+
/// Specify a claim that should be returned from the authentication request.
60+
/// </summary>
61+
/// <param name="claim">See <see cref="Claims"/>.</param>
62+
/// <param name="essential">Indicates whether the Claim being requested is an Essential Claim. If the value is true, this indicates that the Claim is an Essential Claim.</param>
63+
/// <returns>This class for chaining.</returns>
64+
public RequestClaimsParameterValue IdTokenClaim(string claim, bool essential = false)
65+
{
66+
_claims.Add(claim, AsNode(essential, null, null));
67+
return this;
68+
}
69+
70+
/// <summary>
71+
/// Specify a claim that should be returned from the authentication request.
72+
/// </summary>
73+
/// <param name="claim">See <see cref="Claims"/>.</param>
74+
/// <param name="value">Requests that the Claim be returned with a particular value.</param>
75+
/// <param name="essential">Indicates whether the Claim being requested is an Essential Claim. If the value is true, this indicates that the Claim is an Essential Claim.</param>
76+
/// <returns>This class for chaining.</returns>
77+
public RequestClaimsParameterValue IdTokenClaim(string claim, string value, bool essential = false)
78+
{
79+
_claims.Add(claim, AsNode(essential, value, null));
80+
return this;
81+
}
82+
83+
/// <summary>
84+
/// Specify a claim that should be returned from the authentication request.
85+
/// </summary>
86+
/// <param name="claim">See <see cref="Claims"/>.</param>
87+
/// <param name="values">Requests that the Claim be returned with one of a set of values, with the values appearing in order of preference.</param>
88+
/// <param name="essential">Indicates whether the Claim being requested is an Essential Claim. If the value is true, this indicates that the Claim is an Essential Claim.</param>
89+
/// <returns>This class for chaining.</returns>
90+
public RequestClaimsParameterValue IdTokenClaim(string claim, IReadOnlyCollection<string> values, bool essential)
91+
{
92+
_claims.Add(claim, AsNode(essential, null, values));
93+
return this;
94+
}
95+
96+
/// <summary>
97+
/// Convert the current settings to a <see cref="OpenIddictParameter"/> that can be used in a request.
98+
/// <example>
99+
/// <code>
100+
/// new AuthenticationProperties().SetParameter(Parameters.Claims, new RequestClaimsParameterValue().IdTokenClaim(Claims.AuthenticationTime, true).AsOpenIddictParameter());
101+
/// </code></example>
102+
/// </summary>
103+
/// <returns></returns>
104+
public OpenIddictParameter? AsOpenIddictParameter()
105+
{
106+
if (_userinfo.Count == 0 && _claims.Count == 0)
107+
return null;
108+
var lst = new List<KeyValuePair<string, JsonNode?>>();
109+
110+
if (_userinfo.Count != 0)
111+
lst.Add(new("userinfo", new JsonObject(_userinfo)));
112+
if (_claims.Count != 0)
113+
lst.Add(new(Parameters.IdToken, new JsonObject(_claims)));
114+
return new OpenIddictParameter(new JsonObject(lst));
115+
}
116+
117+
private static IEnumerable<KeyValuePair<string, JsonNode?>> GetJsonNode(bool essential, string? value, IReadOnlyCollection<string?>? values)
118+
{
119+
if (essential)
120+
yield return new("essential", true);
121+
if (value is not null)
122+
yield return new("value", value!);
123+
if (values is not null)
124+
yield return new("values", new JsonArray(values.Select(x => (JsonNode?)JsonValue.Create(x)).ToArray()));
125+
}
126+
127+
128+
}
129+
#endif

0 commit comments

Comments
 (0)