35

I am trying to implement "Role Based Authorization" using IdentityServer4 to give access to my API based on the user roles.

For example , I want to have two roles for the user i.e. FreeUser and PaidUser and want to give access to the API through the Authorize Attribute using [Authorize(Roles = "FreeUser"))], Kindly help me that How can I achieve this.

I have the following solution structure :

  1. IdentityServer
  2. WebApi
  3. Javascript Client

I have registered my Javascript client as follows:

 new Client
            {
                ClientId = "js",
                ClientName = "javascript client",
                AllowedGrantTypes = GrantTypes.Implicit,
                AllowAccessTokensViaBrowser= true,
                RedirectUris = {"http://localhost:5004/callback.html"},
                PostLogoutRedirectUris = {"http://localhost:5004/index.html"},
                AllowedCorsOrigins = {"http://localhost:5004"},

                AllowedScopes =
                {
                    StandardScopes.OpenId.Name,
                    StandardScopes.Profile.Name,
                    "api1",
                    "role",
                    StandardScopes.AllClaims.Name
                }
            }

Scopes

 return new List<Scope>
        {
            StandardScopes.OpenId,
            StandardScopes.Profile,

            new Scope
            {
                Name = "api1",
                Description = "My API"
            },
           new Scope
           {
               Enabled = true,
               Name  = "role",
               DisplayName = "Role(s)",
               Description = "roles of user",
               Type = ScopeType.Identity,
               Claims = new List<ScopeClaim>
               {
                   new ScopeClaim("role",false)
               }
           },
           StandardScopes.AllClaims
        };

Users

 return new List<InMemoryUser>
        {
            new InMemoryUser
            {
                Subject = "1",
                Username = "alice",
                Password = "password",

                Claims = new List<Claim>
                {
                    new Claim("name", "Alice"),
                    new Claim("website", "https://alice.com"),
                    new Claim("role","FreeUser")
                }
            },
            new InMemoryUser
            {
                Subject = "2",
                Username = "bob",
                Password = "password",

                Claims = new List<Claim>
                {
                    new Claim("name", "Bob"),
                    new Claim("website", "https://bob.com"),
                    new Claim("role","PaidUser")
                }
            }
        };

WebApi Startup.cs

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


        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
        app.UseCors("default");
        app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
        {
            Authority = "http://localhost:5000",
            ScopeName = "api1",
            //  AdditionalScopes = new List<string> { "openid","profile", "role" },
            RequireHttpsMetadata = false
        });

        app.UseMvc();
    }

Web Api controller

namespace Api.Controllers
{
 [Route("[controller]")]

public class IdentityController : ControllerBase
{
    [HttpGet]
    [Authorize(Roles = "PaidUser")]
    public IActionResult Get()
    {
        return new JsonResult(from c in User.Claims select new { c.Type,    c.Value });
    }

    [Authorize(Roles = "FreeUser")]
    [HttpGet]
    [Route("getfree")]
    public IActionResult GetFreeUser()
    {
        return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
    }
}
}

Javascript Client app.js Here I am trying to login the user through IdentityServer and make an API Request.

var mgr = new Oidc.UserManager(config);
mgr.getUser().then(function (user) {
if (user) {
    log("User logged in", user.profile);
} else {
    log("User is not logged in.");
}
});

function login() {
  mgr.signinRedirect();
 }

function api() {
mgr.getUser().then(function (user) {
    var url = "http://localhost:5001/identity/getfree";

    var xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.onload = function () {
        log(xhr.status, JSON.parse(xhr.responseText));
    };

    xhr.setRequestHeader("Authorization", "Bearer " + user.access_token);
    xhr.send();
  });
 }

 function logout() {
   mgr.signoutRedirect();
 }

The Login flow works fine and I can login successfully, and I can receive the role in the access token.

enter image description here

When I make a request to the API by clicking the button (Call Api) then I get the following error.. enter image description here

muhammad waqas
  • 742
  • 1
  • 5
  • 20
  • This is an older question but were you using both `IdentityServer4` and `Asp.Net Core Identity` or just `IdentityServer4`? – WBuck Feb 13 '20 at 02:02

4 Answers4

14

Given that you have not provided config object for javascript client, I assume you have scope configured as follows.

scope:"openid profile api1 role"

I believe that the main reason for your issue is that role claim is not included in your access token.

Add role claim to api1 scope as follows to include it in the access token.

             new Scope
                {
                    Name = "api1",
                    DisplayName = "API1 access",
                    Description = "My API",
                    Type = ScopeType.Resource,
                    IncludeAllClaimsForUser = true,
                    Claims = new List<ScopeClaim>
                    {
                        new ScopeClaim(ClaimTypes.Name),
                        new ScopeClaim(ClaimTypes.Role)
                    }
                }

You can read my answer here for help debug the issue. implementing roles in identity server 4 with asp.net identity

The complete working solution is here. https://github.com/weliwita/IdentityServer4.Samples/tree/40844310

Community
  • 1
  • 1
rawel
  • 2,923
  • 21
  • 33
  • great..it works fine now.. I can't believe how I forgot to add that to Scopes.. thanks anyway.. – muhammad waqas Nov 30 '16 at 06:13
  • @muhammadwaqas I think you should accept the answer – Learner Jul 04 '17 at 13:16
  • 3
    Today in 2018 with IdentityServer4.AspNetIdentity Version="2.1.0" Scope class was replaced by ApiResource, so I trying to make some adptative, and I can't do it work. Someone implement it for `2.1.0` version? – Alexsandro Jul 22 '18 at 16:37
8

Change new Claim("role","FreeUser") to new Claim(ClaimTypes.Role, "FreeUser")

Or create a policy like this:

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

and use it :

[Authorize(Policy = "FreeUser")]
Chris Marisic
  • 32,487
  • 24
  • 164
  • 258
adem caglin
  • 22,700
  • 10
  • 58
  • 78
  • Thanks for your response, I have tried both the methods but the result is same, in case of adding policy when I make an API request, it makes two requests to the API , the first one says 204 [No Content] status code while the second one gets failed with 403.. In case of Claims the result is again same 403..is there something I am missing?.. – muhammad waqas Nov 28 '16 at 18:40
1

I wrote a sample on this post

Identity Server 4: adding claims to access token

I have tested with Roles and claims also I can use [Authorize(Role="SuperAdmin, Admin")] in both client web app and API app.

Community
  • 1
  • 1
Mohammad Karimi
  • 4,151
  • 2
  • 21
  • 19
0

I got roles working like this in .NET 5:

Add JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); before services.AddAuthentication in Startup.cs.

I also added

services.AddScoped<IProfileService, ProfileService>();

and ProfileService.cs that looks like this to map roles to claims:

public sealed class ProfileService : IProfileService
{
    private readonly IUserClaimsPrincipalFactory<ApplicationUser> _userClaimsPrincipalFactory;
    private readonly UserManager<ApplicationUser> _userMgr;
    private readonly RoleManager<IdentityRole> _roleMgr;

    public ProfileService(
        UserManager<ApplicationUser> userMgr,
        RoleManager<IdentityRole> roleMgr,
        IUserClaimsPrincipalFactory<ApplicationUser> userClaimsPrincipalFactory)
    {
        _userMgr = userMgr;
        _roleMgr = roleMgr;
        _userClaimsPrincipalFactory = userClaimsPrincipalFactory;
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        string sub = context.Subject.GetSubjectId();
        ApplicationUser user = await _userMgr.FindByIdAsync(sub);
        ClaimsPrincipal userClaims = await _userClaimsPrincipalFactory.CreateAsync(user);

        List<Claim> claims = userClaims.Claims.ToList();
        claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList();

        if (_userMgr.SupportsUserRole)
        {
            IList<string> roles = await _userMgr.GetRolesAsync(user);
            foreach (var roleName in roles)
            {
                claims.Add(new Claim(JwtClaimTypes.Role, roleName));
                if (_roleMgr.SupportsRoleClaims)
                {
                    IdentityRole role = await _roleMgr.FindByNameAsync(roleName);
                    if (role != null)
                    {
                        claims.AddRange(await _roleMgr.GetClaimsAsync(role));
                    }
                }
            }
        }

        context.IssuedClaims = claims;
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        string sub = context.Subject.GetSubjectId();
        ApplicationUser user = await _userMgr.FindByIdAsync(sub);
        context.IsActive = user != null;
    }
}

Source:

https://ffimnsr.medium.com/adding-identity-roles-to-identity-server-4-in-net-core-3-1-d42b64ff6675

Ogglas
  • 62,132
  • 37
  • 328
  • 418