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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.