11

I have a SPA that has an ASP.NET Core web API together with the inbuilt identity server switched on using AddIdentityServer and then AddIdentityServerJwt:

services.AddIdentityServer()
   .AddApiAuthorization<User, UserDataContext>();
services.AddAuthentication()
   .AddIdentityServerJwt();

I also have an authorization policy setup that requires an "Admin" role claim:

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

I have a controller action that uses this policy

[Authorize(Policy = "IsAdmin")]
[HttpDelete("{id}")]
public IActionResult Deleten(int id)
{
    ...
}

The authenticated user does have the "Admin" role claim:

enter image description here

The access token for this authentication user doesn't appear to contain the admin claim:

enter image description here

I get a 403 back when trying to request this resource with the admin user:

enter image description here

So, if I'm understanding this correctly, IdentityServer isn't including the admin role claim and so the user isn't authorized to access the resource.

Is it possible to configure the claims that IdentityServer uses using AddIdentityServerJwt? or am I misunderstanding why this is not working.

Carl Rippon
  • 4,553
  • 8
  • 49
  • 64
  • you need to add the claim to your token. I will post you some code in the morning – Linda Lawton - DaImTo May 23 '19 at 20:21
  • I believe this is because the implementation doesn't automatically map the custom claims. See my question regarding what I've found so far: https://stackoverflow.com/questions/56368522/why-does-an-added-claim-seem-to-be-lost-in-asp-net-core-3-0-preview-5 – conciliator May 29 '19 at 21:30

3 Answers3

7

One of the other answers is really close to the specific use case in question but misses the point about it being SPA.

Firstly you must add your IProfileService implementation like suggested already:

public class MyProfileService : IProfileService
{
    public MyProfileService()
    { }

    public Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        //get role claims from ClaimsPrincipal 
        var roleClaims = context.Subject.FindAll(JwtClaimTypes.Role);

        //add your role claims 
        context.IssuedClaims.AddRange(roleClaims);
        return Task.CompletedTask;
    }

    public Task IsActiveAsync(IsActiveContext context)
    {
        // await base.IsActiveAsync(context);
        return Task.CompletedTask;
    }
}

But then go ahead and do this:

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

And your claim will be exposed on the JWT. Replace the ClaimTypes.Role constant with any string corresponding to the claim type you want to expose.

JKJ
  • 535
  • 4
  • 14
5

On Identity Server side , you can create Profile Service to make IDS4 include role claim when issuing tokens .

You can get role claims from ClaimsPrincipal or get the roles from database and create profile service like :

public class MyProfileService : IProfileService
{
    public MyProfileService()
    { }

    public Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        //get role claims from ClaimsPrincipal 
        var roleClaims = context.Subject.FindAll(JwtClaimTypes.Role);

        //add your role claims 
        context.IssuedClaims.AddRange(roleClaims);
        return Task.CompletedTask;
    }

    public Task IsActiveAsync(IsActiveContext context)
    {
        // await base.IsActiveAsync(context);
        return Task.CompletedTask;
    }
}

And register in Startup.cs:

services.AddTransient<IProfileService, MyProfileService>();

On client side , you should map the role claim from your JWT Token and try below config in AddOpenIdConnect middleware :

  options.ClaimActions.MapJsonKey("role", "role", "role");
  options.TokenValidationParameters.RoleClaimType = "role";

Then your api could validate the access token and authorize with role policy .

Nan Yu
  • 26,101
  • 9
  • 68
  • 148
  • 3
    Unfortunately I'm not using the AddOpenIdConnect middleware because this is a SPA. I have the web API with the identity server together by using AddIdentityServerJwt. I'm struggling to work out how I can the configure the claims with AddIdentityServerJwt because it doesn't seem to have any config options – Carl Rippon May 24 '19 at 16:35
  • @CarlRippon I think there should be no need for having to configure OpenIdConnect. I've [answered my own post](https://stackoverflow.com/a/56433648/182048) if you want to have a look. – conciliator Jun 03 '19 at 19:47
0

I did this without using roles but with using a special claim added to the users token. I have created a CustomUserClaimsPrincipalFactory this allows me to add additional claims to the user.

register

services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, CustomUserClaimsPrincipalFactory>();

the code.

public class CustomUserClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole<long>>
    {
        public CustomUserClaimsPrincipalFactory(
            UserManager<ApplicationUser> userManager,
            RoleManager<IdentityRole<long>> roleManager,
            IOptions<IdentityOptions> optionsAccessor)
            : base(userManager, roleManager, optionsAccessor)
        {
        }

        protected override async Task<ClaimsIdentity> GenerateClaimsAsync(ApplicationUser user)
        {  
            var userId = await UserManager.GetUserIdAsync(user);
            var userName = await UserManager.GetUserNameAsync(user);
            var id = new ClaimsIdentity("Identity.Application", 
                Options.ClaimsIdentity.UserNameClaimType,
                Options.ClaimsIdentity.RoleClaimType);
            id.AddClaim(new Claim(Options.ClaimsIdentity.UserIdClaimType, userId));
            id.AddClaim(new Claim(Options.ClaimsIdentity.UserNameClaimType, user.Name));
            id.AddClaim(new Claim("preferred_username", userName));
            id.AddClaim(new Claim("culture", user.Culture ?? "da-DK"));
            if (UserManager.SupportsUserSecurityStamp)
            {
                id.AddClaim(new Claim(Options.ClaimsIdentity.SecurityStampClaimType,
                    await UserManager.GetSecurityStampAsync(user)));
            }
            if (UserManager.SupportsUserClaim)
            {
                id.AddClaims(await UserManager.GetClaimsAsync(user));
            }

            if(user.IsXenaSupporter)
               id.AddClaim(new Claim("supporter", user.Id.ToString()));
            return id;
        }
    }

policy

services.AddAuthorization(options =>
            {
                options.AddPolicy("Supporter", policy => policy.RequireClaim("supporter"));
            });

usage

    [Authorize(AuthenticationSchemes = "Bearer", Policy = "Supporter")]
    [HttpPost("supporter")]
    public async Task<ActionResult> ChangeToSpecificUser([FromBody] ChangeUserRequest request)
    {

     // .................. 

    }
Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449