Skip to content

Commit

Permalink
Refactored the use of ClaimsLite and ClaimsRecord into a single type
Browse files Browse the repository at this point in the history
  • Loading branch information
Erwinvandervalk committed Feb 4, 2025
1 parent 68ef1f8 commit 97db29d
Show file tree
Hide file tree
Showing 10 changed files with 91 additions and 35 deletions.
2 changes: 1 addition & 1 deletion bff/samples/Apis/Api.DPoP/Api.DPoP.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFrameworks>net9.0</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion bff/samples/Bff/Bff.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFrameworks>net9.0</TargetFrameworks>
<RootNamespace>Bff</RootNamespace>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
14 changes: 14 additions & 0 deletions bff/samples/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<!-- This line is needed because:
Aspire needs a single target framework
(until this is resolved: https://github.com/dotnet/aspire/issues/2962)
Strangely enough, <TargetFrameworks>net9.0</TargetFrameworks> in the csproj file is not enough.
But, to use a Directory.Packages.props where we check for $(TargetFramework) == 'net9.0',
we need to set the TargetFramework to 'net9.0' here.
-->
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<Import Project="../../samples.props" />
</Project>
2 changes: 1 addition & 1 deletion bff/samples/Hosts.Tests/Hosts.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFrameworks>net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
Expand Down
41 changes: 41 additions & 0 deletions bff/src/Duende.Bff.Blazor.Client/ClaimLite.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Text.Json.Serialization;

namespace Duende.Bff;

/// <summary>
/// Serialization friendly claim.
///
/// Note, this is a copy of the ClaimsLite class from Duende.Bff, but since we can't create a reference to it, we need to copy it here.
/// We also can't link to it (as we do with the extensions) because we had to make it public.
/// </summary>
internal class ClaimLite()
{
/// <summary>
/// Serialization friendly claim
/// </summary>
/// <param name="type">The type</param>
/// <param name="value">The Value</param>
internal ClaimLite(string type, object value) : this()
{
Type = type;
Value = value;
}

/// <summary>
/// The type
/// </summary>
[JsonPropertyName("type")]
public string Type { get; init; } = default!;

/// <summary>
/// The value
/// </summary>
[JsonPropertyName("value")]
public object Value { get; init; } = default!;

/// <summary>
/// The value type
/// </summary>
[JsonPropertyName("valueType")]
public string? ValueType { get; init; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
</PropertyGroup>

<ItemGroup>
<Compile Include="..\Duende.Bff\Shared\ClaimLite.cs" Link="Shared\ClaimLite.cs" />
<Compile Include="..\Duende.Bff\Shared\ClaimsLiteExtensions.cs" Link="Shared\ClaimsLiteExtensions.cs" />
<Compile Include="..\Duende.Bff\Shared\ClaimsPrincipalLite.cs" Link="Shared\ClaimsPrincipalLite.cs" />
</ItemGroup>
Expand Down
5 changes: 1 addition & 4 deletions bff/src/Duende.Bff.Blazor.Client/Internals/GetUserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,12 @@ public async ValueTask<ClaimsPrincipal> GetUserAsync(bool useCache = true)
return _cachedUser;
}

// TODO - Consider using ClaimLite instead here
record ClaimRecord(string Type, object Value);

internal async Task<ClaimsPrincipal> FetchUser()
{
try
{
_logger.LogInformation("Fetching user information.");
var claims = await _client.GetFromJsonAsync<List<ClaimRecord>>("bff/user?slide=false");
var claims = await _client.GetFromJsonAsync<List<ClaimLite>>("bff/user?slide=false");

var identity = new ClaimsIdentity(
nameof(BffClientAuthenticationStateProvider),
Expand Down
34 changes: 10 additions & 24 deletions bff/src/Duende.Bff/EndpointServices/User/DefaultUserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
Expand Down Expand Up @@ -71,7 +72,7 @@ public virtual async Task ProcessRequestAsync(HttpContext context)
}
else
{
var claims = new List<ClaimRecord>();
var claims = new List<ClaimLite>();
claims.AddRange(await GetUserClaimsAsync(result));
claims.AddRange(await GetManagementClaimsAsync(context, result));

Expand All @@ -90,9 +91,9 @@ public virtual async Task ProcessRequestAsync(HttpContext context)
/// </summary>
/// <param name="authenticateResult"></param>
/// <returns></returns>
protected virtual Task<IEnumerable<ClaimRecord>> GetUserClaimsAsync(AuthenticateResult authenticateResult)
protected virtual Task<IEnumerable<ClaimLite>> GetUserClaimsAsync(AuthenticateResult authenticateResult)
{
return Task.FromResult(authenticateResult.Principal?.Claims.Select(x => new ClaimRecord(x.Type, x.Value)) ?? Enumerable.Empty<ClaimRecord>());
return Task.FromResult(authenticateResult.Principal?.Claims.Select(x => new ClaimLite(x.Type, x.Value)) ?? Enumerable.Empty<ClaimLite>());
}

/// <summary>
Expand All @@ -101,16 +102,16 @@ protected virtual Task<IEnumerable<ClaimRecord>> GetUserClaimsAsync(Authenticate
/// <param name="context"></param>
/// <param name="authenticateResult"></param>
/// <returns></returns>
protected virtual Task<IEnumerable<ClaimRecord>> GetManagementClaimsAsync(HttpContext context, AuthenticateResult authenticateResult)
protected virtual Task<IEnumerable<ClaimLite>> GetManagementClaimsAsync(HttpContext context, AuthenticateResult authenticateResult)
{
var claims = new List<ClaimRecord>();
var claims = new List<ClaimLite>();

var pathBase = context.Request.PathBase;

var sessionId = authenticateResult.Principal?.FindFirst(JwtClaimTypes.SessionId)?.Value;
if (!String.IsNullOrWhiteSpace(sessionId))
{
claims.Add(new ClaimRecord(
claims.Add(new ClaimLite(
Constants.ClaimTypes.LogoutUrl,
pathBase + Options.LogoutPath.Value + $"?sid={UrlEncoder.Default.Encode(sessionId)}"));
}
Expand All @@ -121,33 +122,18 @@ protected virtual Task<IEnumerable<ClaimRecord>> GetManagementClaimsAsync(HttpCo
{
var expiresInSeconds =
authenticateResult.Properties.ExpiresUtc.Value.Subtract(DateTimeOffset.UtcNow).TotalSeconds;
claims.Add(new ClaimRecord(
claims.Add(new ClaimLite(
Constants.ClaimTypes.SessionExpiresIn,
Math.Round(expiresInSeconds)));
}

if (authenticateResult.Properties.Items.TryGetValue(OpenIdConnectSessionProperties.SessionState, out var sessionState) && sessionState is not null)
{
claims.Add(new ClaimRecord(Constants.ClaimTypes.SessionState, sessionState));
claims.Add(new ClaimLite(Constants.ClaimTypes.SessionState, sessionState));
}
}

return Task.FromResult((IEnumerable<ClaimRecord>)claims);
return Task.FromResult((IEnumerable<ClaimLite>)claims);
}

/// <summary>
/// Serialization-friendly claim
/// </summary>
/// <param name="Type"></param>
/// <param name="Value"></param>
protected record ClaimRecord(string Type, object Value)
{
/// <summary></summary>
[JsonPropertyName("type")]
public string Type { get; init; } = Type;

/// <summary></summary>
[JsonPropertyName("value")]
public object Value { get; init; } = Value;
}
}
20 changes: 18 additions & 2 deletions bff/src/Duende.Bff/Shared/ClaimLite.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,41 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.

using System.Text.Json.Serialization;

namespace Duende.Bff;

/// <summary>
/// Serialization friendly claim
/// </summary>
internal class ClaimLite
public class ClaimLite()
{
/// <summary>
///
/// </summary>
/// <param name="type"></param>
/// <param name="value"></param>
public ClaimLite(string type, object value) : this()
{
Type = type;
Value = value;
}

/// <summary>
/// The type
/// </summary>
[JsonPropertyName("type")]
public string Type { get; init; } = default!;

/// <summary>
/// The value
/// </summary>
public string Value { get; init; } = default!;
[JsonPropertyName("value")]
public object Value { get; init; } = default!;

/// <summary>
/// The value type
/// </summary>
[JsonPropertyName("valueType")]
public string? ValueType { get; init; }
}
5 changes: 4 additions & 1 deletion bff/src/Duende.Bff/Shared/ClaimsLiteExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@

using System.Linq;
using System.Security.Claims;
using System.Text.Json.Serialization;

namespace Duende.Bff;



internal static class ClaimsLiteExtensions
{
/// <summary>
/// Converts a ClaimsPrincipalLite to ClaimsPrincipal
/// </summary>
public static ClaimsPrincipal ToClaimsPrincipal(this ClaimsPrincipalLite principal)
{
var claims = principal.Claims.Select(x => new Claim(x.Type, x.Value, x.ValueType ?? ClaimValueTypes.String))
var claims = principal.Claims.Select(x => new Claim(x.Type, x.Value.ToString() ?? string.Empty, x.ValueType ?? ClaimValueTypes.String))
.ToArray();
var id = new ClaimsIdentity(claims, principal.AuthenticationType, principal.NameClaimType,
principal.RoleClaimType);
Expand Down

0 comments on commit 97db29d

Please sign in to comment.