Aspnetcore.docs: Role configuration in ConfigureServices method

Created on 9 Oct 2019  Â·  22Comments  Â·  Source: dotnet/AspNetCore.Docs

I think there is an error in the example in the "Add Role services to Identity" section.
I had to change role configuration in ConfigureServices method from:

services.AddDefaultIdentity<IdentityUser>()
        .AddRoles<IdentityRole>()

to:

services.AddIdentity<IdentityUser, IdentityRole>()

Otherwise, a user's role was accessible using RoleManager, but it was not possible using AuthorizeAttribute on a controller level.


Document Details

⚠ Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

PU Source - Docs.ms doc-bug

Most helpful comment

I'm using the .NET core 3.1 angular app template on a project and I cannot get roles to work either. I've even tried adding the role via a custom profile provider and the [Authorize(Roles='...')] just doesn't work. I'm about to lose my mind. 12 years of being a .NET developer and I've wasted over a day on something as simple as this. Also to note, is that when I add it via the custom profile provider, the role is in there if I look in User.Identity.Claims, however the Authorize attribute still will cause a 403 if I check for the role that is there. Something is seriously broken with this.

All 22 comments

@HaoK please review

Hrm, that seems odd, I will take a deeper look in case this is a product bug

@inatlewski what version of ASP.NET Core are you using?

@hexiaokai , the newest one: 3.0.100

I'm using ASP.Net Core 2.2 and neither the version in the documentation or the proposed change by inatlewski work. They actually stop the site from even starting. I have to reboot my device, and remove that option, to get the site working again.

It's the same in asp.net core 3.1.100-preview-014459
I have open a thread here. In my case, using AddDefaultIdentity() does roles are not added to the database for the new registered user. And this poses another problem on IdentityServer routing if I don't use AddDefaultIdentity().

I have the same problem as @inatlewski with AuthorizeAttribute not working. I'm using asp.net core 3.1.100. Is there a solution for this issue?

@spalek-edhouse what's your startup look like, and what's your authorize attribute look like that doesn't work?

I have used VS template ASP.Net Core Web Application and selected Angular and Individual User accounts in Authentication. That created a project where I modified Startup.cs by just adding

.AddRoles<IdentityRole>() like so:

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

            services.AddDefaultIdentity<ApplicationUser>()
                .AddRoles<IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>();

            services.AddIdentityServer()
                .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();

            services.AddAuthentication()
                .AddIdentityServerJwt();
            services.AddControllersWithViews();
            services.AddRazorPages();
            // In production, the Angular files will be served from this directory
            services.AddSpaStaticFiles(configuration =>
            {
                configuration.RootPath = "ClientApp/dist";
            });
        }

Then I've added the test user:

internal async Task SeedTestDataAsync(UserManager<ApplicationUser> userManager,
            RoleManager<IdentityRole> roleManager)
        {
            ApplicationUser appUser = new ApplicationUser{ UserName = "[email protected]", Email = "[email protected]"};
            IdentityRole appRole = new IdentityRole("Administrator");

            await userManager.CreateAsync(appUser, "P@ssw0rd1");
            await roleManager.CreateAsync(appRole);
            await userManager.AddToRolesAsync(appUser, new List<string> { appRole.Name});
        }

And added attribute in the controller [Authorize(Roles = "Administrator")]

[HttpGet]
        [Authorize(Roles = "Administrator")]
        public IEnumerable<WeatherForecast> Get()
        {
            bool isAdministrator = User.IsInRole("Administrator");

            var rng = new Random();
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }

Now when I try to call the endpoint by simply navigating in the generated UI I've got an 403 Forbidden error response.
If I remove the attribute then it jumps in the method but the claims on the User.Identity does not show any roles and also the check User.IsInRole("Administrator"); returns false.
When I check the DB everithing is in place User, Role and UserRoles have correct records.

Am I missing something?

@javiercn are we missing something on the API auth side to pick up the roles?

            services.AddIdentityServer()
                .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();

Does this need an overload that takes Roles as well so the role claims are added for the user?

The problem is the API Resource isn't requesting the role claim by default...
Adding the following seems to fix it...

services.AddIdentityServer()
        .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(cfg =>
        {
            cfg.ApiResources["**APPNAME**API"].UserClaims.Add("role");
        });

You can also use a Custom Profile Service to manually add a role claim as you can see here:
https://stackoverflow.com/questions/58372961/authorization-role-policy-attributes-not-working-in-net-core-3

@blowdart fyi we might be missing something in the templates to use roles with the API templates

@levitatejay I've modified the code that I posted previously as you suggested and it has the same result as before. No role claims...
2020-01-15_07-02-04
I'll give a shot to Custom Profile Service but it looks like the default way is not working properly.

I'm using the .NET core 3.1 angular app template on a project and I cannot get roles to work either. I've even tried adding the role via a custom profile provider and the [Authorize(Roles='...')] just doesn't work. I'm about to lose my mind. 12 years of being a .NET developer and I've wasted over a day on something as simple as this. Also to note, is that when I add it via the custom profile provider, the role is in there if I look in User.Identity.Claims, however the Authorize attribute still will cause a 403 if I check for the role that is there. Something is seriously broken with this.

I had to use a workaround and use policies [Authorize(Policy ='')] instead of roles. Roles are just not working.

I as well got this working using a policy. A few notes on this. I still had to use the profile service to add the role claim to the JWT. I'm adding it as a JwtClaimTypes.Role:
roleClaims.Add(new Claim(JwtClaimTypes.Role, role));

Even though I add it as a JwtClaimTypes.Role, when I setup the policy, I had to require the claim be a ClaimTypes.Role instead:

services.AddAuthorization(options => { options.AddPolicy("IsAdmin", policy => { policy.RequireClaim(ClaimTypes.Role, "Administrator"); }); options.AddPolicy("IsReviewer", policy => { policy.RequireClaim(ClaimTypes.Role, "Reviewer"); }); });

Doing this got the Authorize(Policy = "IsAdmin") to finally work.

I'll have to say this is really really inexcusable and not the level of product nor documentation I've come to expect from Microsoft over the years. This is like creating a car that the door locks don't quite work right on unless you do it some super specific way and expecting people to just figure it out. Authentication/Authorization is a MAJOR component of any application. One of the main reasons I've stayed a Microsoft developer is that until recently I've felt Microsoft's documentation and examples have always been unparalleled. I feel like this has slipped greatly with a lot of the latest .NET Core related stuff.

@SteveSandersonMS demonstrated using roles here https://youtu.be/RsPXkgOL2gI?t=1979 However no explanation was given on how to make roles available.

The suggestions in the comments above do not seem to work, so any additional explanation would be helpful.

@blowdart can elaborate, but the basic idea is that we are recommending/encouraging moving away from Roles as claims towards a full claim based approach, so that's why the new SPA templates do not make it easy to continue using the old style roles approach. @Rick-Anderson do you think we can add a snippet to this effect somewhere appropriate in the docs?

you can use this to get it working:

services.AddIdentityServer()
                .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(opt => {
                    opt.Clients["TestApi"].AlwaysIncludeUserClaimsInIdToken = true;
                    if (!opt.ApiResources["TestApiAPI"].UserClaims.Contains(JwtClaimTypes.Role))
                    {
                        opt.ApiResources["TestApiAPI"].UserClaims
                            .Add(JwtClaimTypes.Role);
                    }
                });

opt.Clients["TestApi"].AlwaysIncludeUserClaimsInIdToken = true;

This adds all requested user claims to the access_token. Causion here if you have many claims the token could get huge. By default IdentityServer adds only the sub claim, to keep the token small.

and this one says we need the "role" claim for the "TestApiAPI" resource.

if (!opt.ApiResources["TestApiAPI"].UserClaims.Contains(JwtClaimTypes.Role))
{
   opt.ApiResources["TestApiAPI"].UserClaims
       .Add(JwtClaimTypes.Role);
}

Thank you @levitatejay you got me to the right track.

let me know if it works for you guys.

@616b2f it works!
I have question. Role-based auth works, but policy-based auth does not work. In documentation https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-3.1 what we need to work with policy is add this code:

services.AddAuthorization(options =>
    {
        options.AddPolicy("AtLeast21", policy =>
            policy.Requirements.Add(new MinimumAgeRequirement(21)));
    });

and in chosen controller add
[Authorize(Policy = "AtLeast21")]

Anybody has a solution to deal with it?

@SebaPaciorek the problem is that in this tutorial the ClaimTypes.DateOfBirth is used to calculate age of the user. This claim is not in the access_token. You need to do two things:

A: Add this claim to the user
B: Add the claim to the access_token

B could look like this:

if (!opt.ApiResources["TestApiAPI"].UserClaims.Contains(ClaimTypes.DateOfBirth))
 {
                        opt.ApiResources["TestApiAPI"].UserClaims
                            .Add(ClaimTypes.DateOfBirth);
}

Not tested from the top of my head

@SebaPaciorek the problem is that in this tutorial the ClaimTypes.DateOfBirth is used to calculate age of the user. This claim is not in the access_token. You need to do two things:

A: Add this claim to the user
B: Add the claim to the access_token

B could look like this:

if (!opt.ApiResources["TestApiAPI"].UserClaims.Contains(ClaimTypes.DateOfBirth))
 {
                        opt.ApiResources["TestApiAPI"].UserClaims
                            .Add(ClaimTypes.DateOfBirth);
}

Not tested from the top of my head

Yes, it should work.
Alternatively can use .AddClaim in register or login. Tested and works.

Was this page helpful?
0 / 5 - 0 ratings