Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Claims Transformation with Windows Authentication

Tags:

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?

like image 418
Valuator Avatar asked Jun 19 '18 21:06

Valuator


People also ask

How would you implement claims-based authentication in .NET core?

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.

What is claim type in authentication?

Claims-based authentication is a mechanism which defines how applications acquire identity information about users.

What is Aspnetuserclaims?

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.


1 Answers

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.

like image 156
Polina Avatar answered Sep 28 '22 19:09

Polina