Skip to content

Instantly share code, notes, and snippets.

@dj-nitehawk
Last active December 9, 2023 07:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • 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(
"JWT_Signing_Secret_Of_At_Least_32_Characters",
p =>
{
p["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
.AddJWTBearerAuth("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
return Task.FromResult(
userId == "USR001"
? new[] { "Do_Some_Thing", "Do_Another_Thing" }
: Array.Empty<string>());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment