5

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?

1 Answer 1

4

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Upvoted. Pretty good and detailed answer! Hey @valuator This deserves settings this should be The Answer :).

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.