Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Role Based Authorization for Web API with IdentityServer4

I am using IdentityServer4 (v2.2.1) with .Net Core 2.0 and Asp.Net Core Identity. I have three projects in my Solution.

  1. IdentityServer
  2. Web API
  3. MVC Web Application

I am trying to implement Role Based Authorization on my Web API so that any client will pass an Access Token to Web API to access resources.

Currently i can implement Roles bases authorization on MVC Application controller but i cannot pass/Configure the same for WEB API Controller.

Below are the Identity Server Files: Config.cs

public static IEnumerable<ApiResource> GetApiResources()
    {
        return new List<ApiResource>
        {
            //SCOPE - Resource to be protected by IDS 
            new ApiResource("TCSAPI", "TCS API")
            {
                UserClaims = { "role" }
            }
        };
    }


 public static IEnumerable<Client> GetClients()
    {
        return new List<Client>
        {
new Client
            {
                ClientId = "TCSIdentity",
                ClientName = "TCS Mvc Client Application .",
                AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
                RequireConsent = false,
                ClientSecrets =
                {
                    new Secret("secret".Sha256())
                },

                RedirectUris = { "http://localhost:5002/signin-oidc" },
                PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },

                AlwaysSendClientClaims= true,
                AlwaysIncludeUserClaimsInIdToken = true,

                AllowedScopes =
                {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Email,
                    IdentityServerConstants.StandardScopes.Profile,
                    IdentityServerConstants.StandardScopes.OfflineAccess,
                    "TCSAPI",
                    "office",
                    "role",
                },
                AllowOfflineAccess = true
            }
  };
    }

public static IEnumerable<IdentityResource> GetIdentityResources()
    {
        return new IdentityResource[]
        {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
            new IdentityResources.Email(),
            new IdentityResource
            {
                Name = "role",
                DisplayName="User Role",
                Description="The application can see your role.",
                UserClaims = new[]{JwtClaimTypes.Role,ClaimTypes.Role},
                ShowInDiscoveryDocument = true,
                Required=true,
                Emphasize = true
            }
        };
    }

Startup.cs

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

        services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();

        // Add application services.
        services.AddTransient<IEmailSender, EmailSender>();

        services.AddMvc();

        // configure identity server with in-memory stores, keys, clients and scopes
        services.AddIdentityServer()
            .AddDeveloperSigningCredential()
            .AddInMemoryPersistedGrants()
            .AddInMemoryIdentityResources(Config.GetIdentityResources())
            .AddInMemoryApiResources(Config.GetApiResources())
            .AddInMemoryClients(Config.GetClients())
            .AddAspNetIdentity<ApplicationUser>();
    }

MVC WEB APP (Roles Base Authorization works well for MVC WEB APP):

RoleClaimAction.cs using this file to Add roles to Identity.

internal class RoleClaimAction : ClaimAction
{
    public RoleClaimAction()
        : base("role", ClaimValueTypes.String)
    {
    }

    public override void Run(JObject userData, ClaimsIdentity identity, string issuer)
    {
        var tokens = userData.SelectTokens("role");
        IEnumerable<string> roles;

        foreach (var token in tokens)
        {
            if (token is JArray)
            {
                var jarray = token as JArray;
                roles = jarray.Values<string>();
            }
            else
                roles = new string[] { token.Value<string>() };

            foreach (var role in roles)
            {
                Claim claim = new Claim("role", role, ValueType, issuer);
                if (!identity.HasClaim(c => c.Subject == claim.Subject
                                         && c.Value == claim.Value))
                {
                    identity.AddClaim(claim);
                }
            }
        }
    }
} 

MVC WEB APP/Startup.cs

 public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();

        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
        services.AddCors();
        services.AddAuthentication(options =>
        {
            options.DefaultScheme = "Cookies";
            options.DefaultChallengeScheme = "oidc";
        })
            .AddCookie("Cookies")
            .AddOpenIdConnect("oidc", options =>
            {
                options.SignInScheme = "Cookies";
                options.Authority = "http://localhost:5000";
                options.RequireHttpsMetadata = false;

                options.ClientId = "TCSIdentity";

                //HYBRID FLOW
                options.ClientSecret = "secret";

                options.ClaimActions.Add(new RoleClaimAction()); // <-- 

                options.ResponseType = "code id_token token";
                options.GetClaimsFromUserInfoEndpoint = true;
                options.Scope.Add("TCSAPI");
                options.Scope.Add("offline_access");
                //END HYBRID FLOW
                options.SaveTokens = true;
                options.Scope.Add("role");

                options.TokenValidationParameters.NameClaimType = "name";
                options.TokenValidationParameters.RoleClaimType = "role";
            });

    }

MVC WEB APP/HomeController.cs this action methos works well with role base authorization but when when i try to pass Token to Web Api to access anythings with Role Base Authorization it cannot authorize. e.g. var content = await client.GetStringAsync("http://localhost:5001/user");

[Authorize(Roles = "User")]
    [Route("user")]
    public async Task<IActionResult> UserAccess()
    {

        var tokenClient = new TokenClient("http://localhost:5000/connect/token", "RoleApi", "secret");
        var tokenResponse = await tokenClient.RequestClientCredentialsAsync("TCSAPI");

        var client = new HttpClient();
        client.SetBearerToken(tokenResponse.AccessToken);
        var content = await client.GetStringAsync("http://localhost:5001/user");

        ViewBag.Json = JArray.Parse(content).ToString();
        return View("json");
    }
    [Authorize(Roles = "Admin")]
    [Route("admin")]
    public async Task<IActionResult> AdminAccess()
    {
        var accessToken = await HttpContext.GetTokenAsync("id_token");

        var client = new HttpClient();
        client.SetBearerToken(accessToken);
        var content = await client.GetStringAsync("http://localhost:5001/admin");
        ViewBag.Json = JArray.Parse(content).ToString();
        return View("json");
    }

WEBAPI/Startup.cs

 public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvcCore()
            .AddAuthorization()
            .AddJsonFormatters();
        services.AddAuthentication("Bearer")
            .AddIdentityServerAuthentication(options =>
            {
                options.Authority = "http://localhost:5000";
                options.RequireHttpsMetadata = false;
                options.ApiName = "TCSAPI";
            });

        services.AddCors(options =>
        {
            options.AddPolicy("default", policy =>
            {
                policy.WithOrigins("http://localhost:5002")
                .AllowAnyHeader()
                .AllowAnyMethod();
            });
        });
    }

WEB API/TestController.cs

[Route("admin")]
    [Authorize(Roles = "Admin")]
    public IActionResult AdminAccess()
    {
        return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
    }
    [Route("user")]
    [Authorize(Roles = "User")]
    public IActionResult UserAccess()
    {
        return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
    }
    [AllowAnonymous]
    [Route("public")]
    public IActionResult PublicAccess()
    {
        return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
    }
like image 756
Fawad Ali Siddiqi Avatar asked May 10 '18 05:05

Fawad Ali Siddiqi


People also ask

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.

What is API scope in IdentityServer4?

This class models an OAuth scope. Enabled. Indicates if this resource is enabled and can be requested. Defaults to true.

What is API resource IdentityServer4?

The two fundamental resource types in IdentityServer are: identity resources: represent claims about a user like user ID, display name, email address etc… API resources: represent functionality a client wants to access.

How does role based authorization work?

Role-based authorization checks specify which roles which the current user must be a member of to access the requested resource. The controller SalaryController is only accessible by users who are members of the HRManager role or the Finance role.


1 Answers

Your code is not exactly a policy based authorization. Yours looks like the .NET Framework Role Based Authorization.

For Policy Based Authorization, you need to do the following things:

1. In the Startup.cs of your Web API project, you need to add something like:

// more code
.AddMvcCore()
            .AddAuthorization(options =>
            {
                options.AddPolicy("Policy1",
                    policy => policy.Requirements.Add(new Policy1Requirement()));
                options.AddPolicy("Policy2",
                    policy => policy.Requirements.Add(new Policy2Requirement()));
                .
                .
                .
                .
            })
 // more code

2. Then you need to have a class for every Policy(X)Requirement:

public class Policy1Requirement : AuthorizationHandler<Policy1Requirement>, IAuthorizationRequirement
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AdminUserRequirement requirement)
    {
        if (!context.User.HasClaim(c => c.Type == "role" && c.Value == "<YOUR_ROLE_FOR_THIS_POLICY>"))
        {
            context.Fail();
        }
        else
        {
            context.Succeed(requirement);
        }
        return Task.FromResult(0);
    }
}

3. And then at the end, where you are applying the policy, you need to have:

[Authorize(Policy = "Policy1")]
public class MyController : Controller
{
.
.    
}

Good luck!

PS:

The names Policy(X) and Policy(X)Requirement are just for clarification. You can use whatever names you want, as long as you implement the proper interface IAuthorizationRequirement, and inherit the class AuthorizationHandler

like image 160
m3n7alsnak3 Avatar answered Jan 02 '23 07:01

m3n7alsnak3