Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to put ASP.net Identity Roles into the Identityserver4 Identity token

Although I am very happy that IdentityServer4 exists and makes life I with regards to authentication much easier in many ways, I've stumbled onto the problem and the many discussions of adding roles to claims within the community.

My requirements are simple:

  1. I develop apps (xamarin forms) who require authentication and authorization
  2. I need a system to store the identity of my users (name, pwd, roles, phone…) -> ASP.net Identity
  3. I need a system to authenticate my users -> IdentityServer 4
  4. Roles per user are very limited (user/admin) and don’t change
  5. I need an API backend and an MVC admin site (asp.net core)
  6. I want to limit access to some API/MVC controllers using [Authorize(Roles = "Admin")]

I've spent countless hours trying different configurations to get asp.net Identity roles to be passed to my MVC application after authentication but without luck. The purpose is as described in point 6.

I've also spent countless hours reading but I have a feeling that the implementation IdentityServer4.AspIdentity regarding roles has changed a lot since the version v1.0.0.

After reading a lot about this, it remains unclear how to actually implement this as it seems that some of the solution described only 2 months ago are no longer valid.

So, for now I believe there are 2 paths:

  1. Role into the identity token by only requesting an identity token and using AlwaysIncludeInIdToken?
  2. Let the client retrieve the roles using the userinfo endpoint and somehow inject them into the httpcontext(?) alowing mvc to check using [Authorize(Roles = "Admin")]?

Anyway, that's my assumptions.

So, please help out and explain/document in detail so we can start implementing this in a durable way? Some workable examples would be great as well.

like image 326
Laobu Avatar asked Jan 16 '17 18:01

Laobu


People also ask

How do I add a role to my identity server?

Roles can be created in AdminUI in order to create the roles you need within your application. By clicking "Add Role", we can create a new role within the AdminUI application.

How do I use reference token in IdentityServer4?

When using reference tokens - IdentityServer will store the contents of the token in a data store and will only issue a unique identifier for this token back to the client. The API receiving this reference must then open a back-channel communication to IdentityServer to validate the token.

Is IdentityServer4 obsolete?

The current version (IdentityServer4 v4. x) will be the last version we work on as free open source. We will keep supporting IdentityServer4 until the end of life of . NET Core 3.1 in November 2022.


1 Answers

So, after investigation I’ve come up with 2 methods to do this:

  1. Include Roles/other claims on the Identity Server Side
  2. Include Roles/other claims on the Client side

Include on Identity Server Side

ravi punjwani provided the answer in ‘How to add additional claims to be included in the access_token using ASP.Net Identity with IdentityServer4. His solution is still in draft but the solution allows you to add any claim before the token is send back to the client. This is the link: How to add additional claims to be included in the access_token using ASP.Net Identity with IdentityServer4

Include on Client side

This one is a little tougher as it involves adding ‘IClaimsTransformer’ in the request pipeline of the client. The result is that per request, the Claimstransformer will retrieve the claims for the user and add it to the User Identity claims (the token). The set-up of the Claimstransformer is not easy as it’s tricky to get DI to work but after lots of research the celow solution does it for me.

The Custom ClaimsTransformer class does the transformation in the Middleware: public class KarekeClaimsTransformer : IClaimsTransformer { private readonly UserManager _userManager;

    public KarekeClaimsTransformer(UserManager<ApplicationUser> userManager)
    {
        _userManager = userManager;
    }

    public async Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
    {
        if (context.Principal.Identity.IsAuthenticated)
        {
            Claim userId = context.Principal.FindFirst("sub");

            if (context.Principal.FindFirst("role") == null && userId != null)
            {
                ApplicationUser user = await _userManager.FindByIdAsync(userId.Value);
                var roles = await _userManager.GetRolesAsync(user);
                foreach (var role in roles)
                {
                    ((ClaimsIdentity)context.Principal.Identity).AddClaim(new Claim(JwtClaimTypes.Role, role,
                        "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"));
                }
            }
        }
        return Task.FromResult(context.Principal).Result;
    }
}

In the Client start-up class you need to Add it to the scope in ConfigureServices

public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services.
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        ...
        services.AddScoped<IClaimsTransformer, KarekeClaimsTransformer>();
        // Add framework services.
        services.AddMvc();
    }

Lastly, add in Configure:

 public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();

        app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
        {
            Authority = "http://localhost:5000",
            RequireHttpsMetadata = false,

            ApiName = "api1"
        });

        app.UseClaimsTransformation((context) =>
        {
            IClaimsTransformer transformer = context.Context.RequestServices.GetRequiredService<IClaimsTransformer>();
            return transformer.TransformAsync(context);
        });

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "api/{controller}/{action?}/{id?}");
        });
    }
like image 59
Laobu Avatar answered Sep 27 '22 23:09

Laobu