C# – ASP.Net Core 2.1 register custom ClaimsPrincipal

asp.net-coreasp.net-identityc

I am creating a Windows Authentication app but the roles sit within the custom database and not on the AD so I created a custom ClaimsPrincipal to override the User.IsInRole() function that usually looks at the AD for roles.

However, when running the application it still seems to be using the original code and not my CustomClaimsPrincipal.
I get the error "The trust relationship between the primary domain and the trusted domain failed".

In ASP.Net MVC 5 I used a Custom RoleProvider which is essentially what I am trying to replicate here.

CustomClaimsPrincipal.cs

public class CustomClaimsPrincipal : ClaimsPrincipal
{
    private readonly ApplicationDbContext _context;

    public CustomClaimsPrincipal(ApplicationDbContext context)
    {
        _context = context;
    }

    public override bool IsInRole(string role)
    {
        var currentUser = ClaimsPrincipal.Current.Identity.Name;

        IdentityUser user = _context.Users.FirstOrDefault(u => u.UserName.Equals(currentUser, StringComparison.CurrentCultureIgnoreCase));

        var roles = from ur in _context.UserRoles.Where(p => p.UserId == user.Id)
                    from r in _context.Roles
                    where ur.RoleId == r.Id
                    select r.Name;
        if (user != null)
            return roles.Any(r => r.Equals(role, StringComparison.CurrentCultureIgnoreCase));
        else
            return false;
    }
}

Startup.cs

        services.AddIdentity<ApplicationUser, IdentityRole>().AddEntityFrameworkStores<ApplicationDbContext>();

        services.AddScoped<ClaimsPrincipal,CustomClaimsPrincipal>();

Not sure if the above code in Startup.cs is the correct way to override the ClaimsPrincipal as I'm new to the .Net Core framework.

Best Answer

I think I would tackle that problem differently: instead of trying to have the instance of ClaimsPrincipal talk to the database to figure out if they belong to a specific role, I would modify the ClaimsPrincipal and add the roles they belong to in the ClaimsPrincipal instance.

To do so, I would use a feature that is unfortunately not well documented. The authentication pipeline exposes an extensibility point where once the authentication is done, you can transform the ClaimsPrincipal instance that was created. This can be done through the IClaimsTransformation interface.

The code could look something like:

public class Startup
{
    public void ConfigureServices(ServiceCollection services)
    {
        // Here you'd have your registrations

        services.AddTransient<IClaimsTransformation, ClaimsTransformer>();
    }
}

public class ClaimsTransformer : IClaimsTransformation
{
    private readonly ApplicationDbContext _context;

    public ClaimsTransformer(ApplicationDbContext context)
    {
        _context = context;
    }

    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        var existingClaimsIdentity = (ClaimsIdentity)principal.Identity;
        var currentUserName = existingClaimsIdentity.Name;

        // Initialize a new list of claims for the new identity
        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.Name, currentUserName),
            // Potentially add more from the existing claims here
        };

        // Find the user in the DB
        // Add as many role claims as they have roles in the DB
        IdentityUser user = await _context.Users.FirstOrDefaultAsync(u => u.UserName.Equals(currentUserName, StringComparison.CurrentCultureIgnoreCase));
        if (user != null)
        {
            var rolesNames = from ur in _context.UserRoles.Where(p => p.UserId == user.Id)
                        from r in _context.Roles
                        where ur.RoleId == r.Id
                        select r.Name;

            claims.AddRange(rolesNames.Select(x => new Claim(ClaimTypes.Role, x)));
        }

        // Build and return the new principal
        var newClaimsIdentity = new ClaimsIdentity(claims, existingClaimsIdentity.AuthenticationType);
        return new ClaimsPrincipal(newClaimsIdentity);
    }
}

For full disclosure, the TransformAsync method will run every time the authentication process takes place, so most likely on every request, also meaning it will query the database on every request to fetch the roles of the logged-in user.

The advantage of using this solution over modifying the implementation of ClaimsPrincipal is that the ClaimsPrincipal is now dumb and not tied to your database. Only the authentication pipeline knows about it, which makes things like testing easier as you could, for example, new-up a ClaimsPrincipal with specific roles to make sure they do or don't have access to specific actions, without being tied to the database.