Skip to content

Instantly share code, notes, and snippets.

@dj-nitehawk
Last active June 15, 2024 04:41
Show Gist options
  • Save dj-nitehawk/220363f14e649a2cb850d61f9bd793b5 to your computer and use it in GitHub Desktop.
Save dj-nitehawk/220363f14e649a2cb850d61f9bd793b5 to your computer and use it in GitHub Desktop.
Dynamic claim/permission hydration instead of embedding everything in JWT Token
sealed class UserPermissionHydrator(UserPermissionService userPermissionService) : IClaimsTransformation
{
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var userId = principal.Claims.FirstOrDefault(c => c.Type == "UserId")?.Value;
ArgumentNullException.ThrowIfNull(userId);
var userPermissions = await userPermissionService.GetPermissionsForUser(userId);
if (userPermissions.Length != 0)
principal.AddIdentity(new(userPermissions.Select(p => new Claim("permissions", p))));
return principal;
}
}
sealed class LoginEndpoint : EndpointWithoutRequest
{
public override void Configure()
{
Get("login");
AllowAnonymous();
}
public override Task HandleAsync(CancellationToken c)
{
var jwtToken = JwtBearer.CreateToken(
o =>
{
o.SigningKey = "JWT_Signing_Secret_Of_At_Least_32_Characters";
o.User.Claims.Add(("UserId", "USR001")); //embed just the user id claim
});
return SendAsync(jwtToken);
}
}
using System.Security.Claims;
using FastEndpoints;
using FastEndpoints.Security;
using FastEndpoints.Swagger;
using Microsoft.AspNetCore.Authentication;
var bld = WebApplication.CreateBuilder(args);
bld.Services
.AddTransient<UserPermissionService>() //register the user permission service
.AddTransient<IClaimsTransformation, UserPermissionHydrator>() //register the claim transformation
.AddAuthenticationJwtBearer(s => s.SigningKey = "JWT_Signing_Secret_Of_At_Least_32_Characters")
.AddAuthorization()
.AddFastEndpoints()
.SwaggerDocument();
var app = bld.Build();
app.UseAuthentication()
.UseAuthorization()
.UseFastEndpoints()
.UseSwaggerGen();
app.Run();
sealed class ProtectedEndpoint : EndpointWithoutRequest
{
public override void Configure()
{
Get("protected");
Permissions("Do_Some_Thing");
}
public override Task HandleAsync(CancellationToken c)
=> SendAsync("You are authorized!");
}
sealed class UserPermissionService
{
public Task<string[]> GetPermissionsForUser(string userId)
//fetch the user's permissions from a db or cache here
=> Task.FromResult(
userId == "USR001"
? ["Do_Some_Thing", "Do_Another_Thing"]
: Array.Empty<string>());
}
@gabriel-rodriguezcastellini

Hello is it possible to execute this code after a preprocessor execution @dj-nitehawk?

@dj-nitehawk
Copy link
Author

@gabriel-rodriguezcastellini
not possible. IClaimsTransformation is part of the asp.net auth middleware and it gets executed way before pre-processors run. pre-processors only run after the user is authenticated and it's too late in the pipeline to populate permissions for the user.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment