Suggested Videos
Part 98 - Claim type and claim value in claims policy based authorization in asp.net core | Text | Slides
Part 99 - Create custom authorization policy using func in asp.net core | Text | Slides
Part 100 - Custom authorization requirements and handlers in asp.net core | Text | Slides
In this video we will discuss creating a custom authorization requirement and a handler in asp.net core with an example.
Our Application Authorization Requirement
An Admin user can manage other Admin user roles and claims but not their own claims and roles.
To achieve this, we need to know the logged-in UserID and the UserId of the Admin being edited. If they are the same we do not want to allow access. The admin UserID being edited is passed in the URL as a query string parameter.
Why create custom requirements and handlers
We can create a custom policy using a func. We discussed the func delegate and RequireAssertion() method in Part 99 of ASP.NET Core tutorial.
However, a func cannot be used to satisfy our authorization requirement here because we need to access the query string parameter. Also as your authorization requirements get complex, you may need access to other services via dependency injection. In situations like these we create custom requirements and handlers.
Creating a custom authorization requirement
To create a custom authorization requirement, create a class that implements the IAuthorizationRequirement interface. This is an empty marker interface, which means there is nothing in this interface that our custom requirement class must implement.
IAuthorizationRequirement interface is in Microsoft.AspNetCore.Authorization namespace.
Creating a custom authorization handler
It is in the authorization handler that we write our logic to allow or deny access to a resource like a controller action for example. To implement a handler you inherit from AuthorizationHandler<T>, and implement the HandleRequirementAsync() method. The generic parameter <T> on the AuthorizationHandler<T> is the type of requirement.
Authorization handler code explanation
Resource property of AuthorizationHandlerContext returns the resource that we are protecting. In our case, we are using this custom requirement to protect a controller action method. So the following line returns the controller action being protected as the AuthorizationFilterContext and provides access to HttpContext, RouteData, and everything else provided by MVC and Razor Pages.
If AuthorizationFilterContext is NULL, we cannot check if the requirement is met or not, so we return Task.CompletedTask and the access is not authorised.
Our requirement is met and the authorization succeeds
If the user is in the Admin role
AND
has Edit Role claim type with a claim value of true
AND
the logged-in user Id is NOT EQUAL TO the Id of the Admin user being edited
Succeed() method specifies that the requirement is successfully evaluated.
Authorization handler registration
We register custom authorization handler in ConfigureServices() method of the Startup class
Finally use the custom policy to protect the resources like controller action methods
Part 98 - Claim type and claim value in claims policy based authorization in asp.net core | Text | Slides
Part 99 - Create custom authorization policy using func in asp.net core | Text | Slides
Part 100 - Custom authorization requirements and handlers in asp.net core | Text | Slides
In this video we will discuss creating a custom authorization requirement and a handler in asp.net core with an example.
Our Application Authorization Requirement
An Admin user can manage other Admin user roles and claims but not their own claims and roles.
To achieve this, we need to know the logged-in UserID and the UserId of the Admin being edited. If they are the same we do not want to allow access. The admin UserID being edited is passed in the URL as a query string parameter.
Why create custom requirements and handlers
We can create a custom policy using a func. We discussed the func delegate and RequireAssertion() method in Part 99 of ASP.NET Core tutorial.
However, a func cannot be used to satisfy our authorization requirement here because we need to access the query string parameter. Also as your authorization requirements get complex, you may need access to other services via dependency injection. In situations like these we create custom requirements and handlers.
Creating a custom authorization requirement
To create a custom authorization requirement, create a class that implements the IAuthorizationRequirement interface. This is an empty marker interface, which means there is nothing in this interface that our custom requirement class must implement.
public class ManageAdminRolesAndClaimsRequirement : IAuthorizationRequirement
{ }
{ }
IAuthorizationRequirement interface is in Microsoft.AspNetCore.Authorization namespace.
Creating a custom authorization handler
It is in the authorization handler that we write our logic to allow or deny access to a resource like a controller action for example. To implement a handler you inherit from AuthorizationHandler<T>, and implement the HandleRequirementAsync() method. The generic parameter <T> on the AuthorizationHandler<T> is the type of requirement.
public class CanEditOnlyOtherAdminRolesAndClaimsHandler :
AuthorizationHandler<ManageAdminRolesAndClaimsRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
ManageAdminRolesAndClaimsRequirement requirement)
{
var authFilterContext = context.Resource as AuthorizationFilterContext;
if (authFilterContext == null)
{
return Task.CompletedTask;
}
string loggedInAdminId =
context.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value;
string adminIdBeingEdited = authFilterContext.HttpContext.Request.Query["userId"];
if (context.User.IsInRole("Admin") &&
context.User.HasClaim(claim => claim.Type == "Edit Role" && claim.Value == "true") &&
adminIdBeingEdited.ToLower() != loggedInAdminId.ToLower())
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
AuthorizationHandler<ManageAdminRolesAndClaimsRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
ManageAdminRolesAndClaimsRequirement requirement)
{
var authFilterContext = context.Resource as AuthorizationFilterContext;
if (authFilterContext == null)
{
return Task.CompletedTask;
}
string loggedInAdminId =
context.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value;
string adminIdBeingEdited = authFilterContext.HttpContext.Request.Query["userId"];
if (context.User.IsInRole("Admin") &&
context.User.HasClaim(claim => claim.Type == "Edit Role" && claim.Value == "true") &&
adminIdBeingEdited.ToLower() != loggedInAdminId.ToLower())
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
Authorization handler code explanation
Resource property of AuthorizationHandlerContext returns the resource that we are protecting. In our case, we are using this custom requirement to protect a controller action method. So the following line returns the controller action being protected as the AuthorizationFilterContext and provides access to HttpContext, RouteData, and everything else provided by MVC and Razor Pages.
var authFilterContext = context.Resource as AuthorizationFilterContext;
If AuthorizationFilterContext is NULL, we cannot check if the requirement is met or not, so we return Task.CompletedTask and the access is not authorised.
if (authFilterContext == null)
{
return Task.CompletedTask;
}
{
return Task.CompletedTask;
}
Our requirement is met and the authorization succeeds
If the user is in the Admin role
AND
has Edit Role claim type with a claim value of true
AND
the logged-in user Id is NOT EQUAL TO the Id of the Admin user being edited
if (context.User.IsInRole("Admin") &&
context.User.HasClaim(claim => claim.Type == "Edit Role" && claim.Value == "true") &&
adminIdBeingEdited.ToLower() != loggedInAdminId.ToLower())
{
context.Succeed(requirement);
}
context.User.HasClaim(claim => claim.Type == "Edit Role" && claim.Value == "true") &&
adminIdBeingEdited.ToLower() != loggedInAdminId.ToLower())
{
context.Succeed(requirement);
}
Succeed() method specifies that the requirement is successfully evaluated.
Authorization handler registration
We register custom authorization handler in ConfigureServices() method of the Startup class
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy("EditRolePolicy", policy =>
policy.AddRequirements(new ManageAdminRolesAndClaimsRequirement()));
});
services.AddSingleton<IAuthorizationHandler,
CanEditOnlyOtherAdminRolesAndClaimsHandler>();
}
{
services.AddAuthorization(options =>
{
options.AddPolicy("EditRolePolicy", policy =>
policy.AddRequirements(new ManageAdminRolesAndClaimsRequirement()));
});
services.AddSingleton<IAuthorizationHandler,
CanEditOnlyOtherAdminRolesAndClaimsHandler>();
}
Finally use the custom policy to protect the resources like controller action methods
[HttpGet]
[Authorize(Policy = "EditRolePolicy")]
public async Task<IActionResult> ManageUserRoles(string userId)
{
// Implementation
}
[Authorize(Policy = "EditRolePolicy")]
public async Task<IActionResult> ManageUserRoles(string userId)
{
// Implementation
}
var authFilterContext = context.Resource as AuthorizationFilterContext;
ReplyDeleteAbove line always returns null in aspnet 3.0
Please help!!
This Worked for me !
ReplyDeletepublic class CanEditOnlyOtherAdminRolesAndClaimsHandler : AuthorizationHandler
{
private readonly IHttpContextAccessor httpContextAccessor;
public CanEditOnlyOtherAdminRolesAndClaimsHandler(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
ManageAdminRolesAndClaimsRequirement requirement)
{
string loggedInAdminId = context.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value.ToString();
string adminIdBeingEdited = httpContextAccessor.HttpContext.Request.Query["userId"].ToString();
if (context.User.IsInRole("Admin") &&
context.User.HasClaim(claim =>
claim.Type == "Edit Role" && claim.Value == "true") && adminIdBeingEdited.ToLower() != loggedInAdminId.ToLower())
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
Thank you for the solution.
Deletepublic class CanEditOnlyOtherAdminRolesAndClaimsHandler :
ReplyDeleteAuthorizationHandler
{
protected override Task HandleRequirementAsync( AuthorizationHandlerContext context,
ManageAdminRolesAndClaimsRequirement requirement )
{
var authFilterContext = context.Resource as AuthorizationFilterContext;
if(authFilterContext == null)
{
return Task.CompletedTask;
}
string loggedInAdminId =
context.User.Claims.FirstOrDefault( c => c.Type == ClaimTypes.NameIdentifier ).Value;
string adminIdBeingEdited = authFilterContext.HttpContext.Request.Query["userId"];
if(context.User.IsInRole( "Admin" ) &&
context.User.HasClaim( claim => claim.Type == "Edit Role" && claim.Value == "true" ) &&
adminIdBeingEdited.ToLower() != loggedInAdminId.ToLower())
{
context.Succeed( requirement );
}
return Task.CompletedTask;
}
}
THI S LINE OF CODE IS NOT WORKING ON ASP.NET CORE 3.1
the solution provided work with me too but I has one question
ReplyDeletewho pass the value for this parameter IHttpContextAccessor httpContextAccessor to the ctr
can any one help
MVC injects the value of httpContextAccessor into the constructor method of CanEditOnlyOtherAdminRolesAndClaimsHandler.
Deletei cant use that, i have error like "InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action configureOptions)."
ReplyDelete