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.
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.