Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Injected IPrincipal Claims empty after adding Claims Transformer

I'm attempting to use Claims based authorization using Windows Authentication in ASP.NET Core. I have a Claims Transformer that sets user Claims. I also inject IPrincipal into by DbContext so I can access the current user when SaveChanges() is called to log who updated entities.

In Startup.cs:

    services.AddTransient<IPrincipal>(provider => provider.GetService<IHttpContextAccessor>()?.HttpContext?.User);
    services.AddTransient<IClaimsTransformation, MyClaimsTransformer>();

In MyDbContext.cs:

    protected readonly IPrincipal _principal ;

    public MyDbContext(DbContextOptions<MyDbContext> options, IPrincipal principal) : base(options)
    {
        _principal = principal;
    }

    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
    {
        // Do something with Claim value...
        await base.SaveChangesAsync(cancellationToken);
    }

I've added a Claims Transformer to add some Claim info:

    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        // Add Claims...
        return principal;
    }

SaveChangesAsync is called in User Middleware to log the access to the application. Before adding the Claims Transformer, the IPrincipal injected into the DbContext was a WindowsPrincipal, and it contained the Claims I needed such as PrimarySid, etc. Now after adding the transformation, it is a ClaimsPrincipal however the list of Claims is now empty.

Through debugging I can verify that TransformAsync executes before SaveChangesAsync, but the constructor injection in the db context occurs before the transformation (The DbContext is a Scoped service, the Claims Transfomer is Transient).

Summary:

  • Before adding the Claims Transformer, the injected IPrincipal is a WindowsPrincipal with a populated list of Claims.
  • After adding the Claims Transformer, the injected IPrincipal is a ClaimsPrincipal with an empty list of Claims.
like image 686
Valuator Avatar asked Jan 17 '26 23:01

Valuator


1 Answers

I had the exact same issue when trying to inject the IPrincipal through the DbContext constructor. I have found that the IPrincipal that was injected in the DbContext was not yet "Transformed" by the ClaimsTransformer.

I have worked around this problem by creating a UserResolverService.

IUserResolverService.cs

public interface IUserResolverService
{
    IPrincipal GetUser();
}

UserResolverService.cs

public class UserResolverService : IUserResolverService
{
    private readonly IHttpContextAccessor _context;

    public UserResolverService(IHttpContextAccessor context)
    {
        _context = context;
    }

    public IPrincipal GetUser()
    {
        return _context.HttpContext.User;
    }
}

MyDbContext.cs

private readonly IUserResolverService _userResolverService;

// Call this whenever you need the IPrincipal object
public IPrincipal User => _userResolverService.GetUser();

public MyDbContext(IUserResolverService userResolverService, DbContextOptions<MyDbContext> options)
        : base(options)
{
    _userResolverService = userResolverService;
}

Startup.cs

services.AddTransient<IClaimsTransformation, ClaimsTransformer>();
services.AddTransient<IUserResolverService, UserResolverService>();
like image 73
jBelanger Avatar answered Jan 19 '26 14:01

jBelanger



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!