Reputation: 421
I am using IdentityServer4 (v2.2.1) with .Net Core 2.0 and Asp.Net Core Identity. I have three projects in my Solution.
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 });
}
Upvotes: 9
Views: 6025
Reputation: 3166
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
Upvotes: 5