Using Windows Authentication in an ASP.NET Core 2.1 application. In the database we have a User
table that stores users along with their Sid. It has a 1-1 relationship with UserProfile
which has information I want to use for Claims.
I added a this service for Claims Transformation:
public class UserStatusClaimsTransformation : IClaimsTransformation
{
private readonly MyDbContext _context;
public UserStatusClaimsTransformation(MyDbContext context)
{
_context = context;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
if (principal.Identity is WindowsIdentity identity)
{
User user = await _context.User
.Include(u => u.UserProfile)
.Where(u => new SecurityIdentifier(u.WindowsSid, 0) == identity.User)
.SingleOrDefaultAsync();
if (user != null)
{
identity.AddClaim(new Claim("Status", user.UserProfile));
}
}
return principal;
}
}
My issue is, once this service is registered, the IPrincipal
accessed elsewhere in the pipeline is now a ClaimsPrincipal
instead of a WindowsPrincipal
. Example, in MyDbContext
I inject IPrincipal
via DI:
public MyDbContext(DbContextOptions<MyDbContext> options, IPrincipal principal) : base(options)
{
_principal = principal;
}
Previously, this was a WindowsPrincipal
and I could get the Username from _principal.Identity.Name
, but after registering my Claims Transformer it is a ClaimsPrincipal
and _principal.Identity.Name
is null. Is there a way to keep the IPrincipal
provided through DI as a WindowsPrincipal
after using the Claims Transformation?
The claims-based authorization works by checking if the user has a claim to access an URL. In ASP.NET Core we create policies to implement the Claims-Based Authorization. The policy defines what claims that user must process to satisfy the policy. We apply the policy on the Controller, action method, razor page, etc.
Claims-based authentication is a mechanism which defines how applications acquire identity information about users.
A claims-based identity is the set of claims. A claim is a statement that an entity (a user or another application) makes about itself, it's just a claim. For example a claim list can have the user's name, user's e-mail, user's age, user's authorization for an action.
I use ASP.NET MVC Core 2.2, and it's probably too late for you, but maybe it would be helpful for someone else. Then I started my application I didn't find helpful information, and it was my custom suggestion. It works, you can use it:
public class ClaimsLoader : IClaimsTransformation
{
private IUserrolesRepository repository;
public ClaimsLoader(IUserrolesRepository repo)
{
repository = repo;
}
public bool ifRoleExist(ClaimsPrincipal principal, string value)
{
var ci = (ClaimsIdentity)principal.Identity;
var claim = principal.FindAll(ci.RoleClaimType);
foreach (var c in claim)
{
if (c.Value == value)
return true;
}
return false;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var ci = (ClaimsIdentity)principal.Identity;
List<Userrole> userroles = await repository.Userroles.Include(u => u.R).Include(u => u.U).Where(u => u.U.Uid==principal.Identity.Name.Substring(10)).ToListAsync();
if (userroles!=null) {
foreach (Userrole ur in userroles)
{
Claim claim = new Claim(ci.RoleClaimType, ur.Rid);
if (!ifRoleExist(principal, ur.Rid))
ci.AddClaim(claim);
}
}
return await Task.FromResult(principal);
}
}
I didn't use ApplicationDbContext here, instead I use repository, this is repository interface:
public interface IUserrolesRepository
{
IQueryable<Userrole> Userroles { get; }
}
and EFUserRepository class:
public class EFUserrolesRepository : IUserrolesRepository
{
private ApplicationDbContext context;
public EFUserrolesRepository(ApplicationDbContext ctx)
{
context = ctx;
}
public IQueryable<Userrole> Userroles => context.Userrole;
}
this is my Userrole class (just in case if someone need it):
public partial class Userrole
{
public string Rid { get; set; }//pk
public string Uid { get; set; }//pk
public virtual Role R { get; set; }//fk
public virtual User U { get; set; }//fk
}
In ApplicationDbContext I did some changes too:
public class ApplicationDbContextFactory
: IDesignTimeDbContextFactory<ApplicationDbContext>
{
public ApplicationDbContext CreateDbContext(string[] args) =>
Program.BuildWebHost(args).Services
.GetRequiredService<ApplicationDbContext>();
}
and finally in Startup:
services.AddSingleton<IClaimsTransformation, ClaimsLoader>();
services.AddTransient<IUserrolesRepository, EFUserrolesRepository>();
services.AddMvc();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddAuthentication(Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.AuthenticationScheme);
and Program.cs file changed too:
public class Program
{
public static void Main(string[] args)
{
//CreateWebHostBuilder(args).Build().Run();
BuildWebHost(args).Run();
}
/*public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();*/
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseDefaultServiceProvider(options =>
options.ValidateScopes = false)
.Build();
}
That's it. It should work.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With