Evaluate AuthorizeAttribute in action - asp.net-core

One can authorize an action by using the [Authorize] attribute. But I want to only perform authorization on the action in specific conditions, so I cannot use this attribute. I don't think I am able to use IAuthorizationService.AuthorizeAsync as I don't have any policy names. Here's my service configuration in Startup.
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration, "AzureAdB2C");
So my question is, how can I move the [Authorize] evaluation into the action code?

The AuthorizeAttribute will be converted (maybe with others) into an AuthorizeFilter and this filter will be executed with some code that is not equivalent to the simple IAuthorizationService.AuthorizeAsync. But if you want to use that anyway, we can get the default policy (which is used by [Authorize] without any policy specified) by using the IAuthorizationPolicyProvider.GetDefaultPolicyAsync. After that you can authorize the User to get an AuthorizationResult. It is succeeded if the property Succeeded is true. Otherwise, you can have the detailed failure in the property Failure (of type AuthorizationFailure). Here's the code:
public class TestController {
readonly IAuthorizationService _authorizationService;
readonly IAuthorizationPolicyProvider _authorizationPolicyProvider;
public TestController(IAuthorizationService authorizationService,
IAuthorizationPolicyProvider authorizationPolicyProvider){
_authorizationService = authorizationService;
_authorizationPolicyProvider = authorizationPolicyProvider;
}
public async Task<IActionResult> SomeAction(){
var defaultPolicy = await _authorizationPolicyProvider.GetDefaultPolicyAsync();
var authResult = await _authorizationService.AuthorizeAsync(User, defaultPolicy);
if(authResult.Succeeded){
//do something ...
}
}
}

Related

OnAuthorizationAsync not being called when creating custom AuthorizeFilter that inherits from AuthorizeFilter

I've created a custom authorize filter which looks like this:
public class BearerTokenAuthorizeFilter : AuthorizeFilter
{
public override async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
await base.OnAuthorizationAsync(context);
if (context.Result is ChallengeResult)
{
// Then return a problem detail
ObjectResult result = new ObjectResult(new ProblemDetails
{
Type = ProblemDetailsTypes.Unauthorized,
Title = ReasonPhrases.GetReasonPhrase(StatusCodes.Status401Unauthorized),
Status = StatusCodes.Status401Unauthorized,
Detail = ProblemDetailsDescriptions.Unauthorized
});
result.ContentTypes.Add(new MediaTypeHeaderValue(new Microsoft.Extensions.Primitives.StringSegment("application/problem+json")));
context.Result = result;
await context.HttpContext.ChallengeAsync();
}
else if (context.Result is ForbidResult)
{
context.Result = new StatusCodeResult(StatusCodes.Status403Forbidden);
await context.HttpContext.ForbidAsync();
}
}
}
I am registering this filter like this:
services.AddMvcCore(options =>
{
options.Filters.Add<BearerTokenAuthorizeFilter>();
});
I have set the default authentication to be 'Bearer':
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
I have added Authorize attribute on the controller. Whenever I send an unauthorized request to the endpoint my custom filter is never called and I have no idea why? My goal is to return problem details if the request is unauthorized to provide a little bit more information to the consumer than just the status code. Why is my filter not being called?
Try implement IAuthorizationFilter or IAsyncAuthorizationFilter instead of AuthorizeFilter. It work for me. Also I noticed that GetFilter(..) method returns AuthorizeFilter instance directly in AuthorizationApplicationModelProvider when filter class implements AuthorizeFilter. But when filter implements IAuthorizationFilter or IAsyncAuthorizationFilter this method being not called I think that is issue in ASP NET
I have ended up implementing my own IControllerModelConvention class which looks like this:
public class BearerTokenAuthorizeConvention : IControllerModelConvention
{
private AuthorizationPolicy _policy;
public BearerTokenAuthorizeConvention(AuthorizationPolicy policy)
{
_policy = policy;
}
public void Apply(ControllerModel controller)
{
if (controller.Filters.OfType<BearerTokenAuthorizeFilter>().FirstOrDefault() == null)
{
//default policy only used when there is no authorize filter in the controller
controller.Filters.Add(new BearerTokenAuthorizeFilter(_policy));
}
}
}
This will be executed once per controller. I then registered this convention like this:
// Configure application filters and conventions
services.Configure<MvcOptions>(options =>
{
AuthorizationPolicy defaultPolicy = new AuthorizationOptions().DefaultPolicy;
options.Conventions.Add(new BearerTokenAuthorizeConvention(defaultPolicy));
});
At this point every controller I have will be tagged with this custom filter which will call base implementation of AuthorizeFilter. The reason why I wanted to derive from AuthorizeFilter was because I wanted to call the default implementation of Authorize and then handle failed response on my own. I thought I could accomplish this very functionality and somehow still be able to only use Authorize attribute. This doesn't seem to be possible. Unless it is an I'm missing something?

Custom attribute validation in ASP.NET Core is running before JWT token is processed

asp.net core authorization
I am trying to use a custom authorization attribute to have finer control over my controller actions like this (somewhat similar to How do you create a custom AuthorizeAttribute in ASP.NET Core?)
[MyCustomAuth(Permissions="Products/Read")]
public IActionResult SomeMethod()
{
.....
}
public class MyCustomAuthAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public string Permissions { get; set; } //Permission string to get from controller
public void OnAuthorization(AuthorizationFilterContext context)
{
//
//read jwttoken
//and process permissions string
//to decide if user can run controller method
//
..
}
}
Unfortunately the JWT authorization handler that is built into ASP.NET core (configured in startup.cs) is run only AFTER this custom attribute is code is run so I can't seem to access the JWT token and THEN process the custom auth parameters.
Is there anyway to force the JWT token to be processed first and then do an extra validation using the custom attribute?
I think I found a solution...it seems to work..but could someone please confirm this is the right way?
Just implement IOrderedFilter interface and set Order to a high number. This means JWT authentication will be called first and then your custom authorization filter.
public class MyCustomAuthAttribute : AuthorizeAttribute, IAuthorizationFilter
{
...
public int Order => 9999;
...
}

How to implement Handler level authorization in asp.net core RazorPages

Authorize attribute at PageModel is not enough because it applies to all handlers in that pagemodel.
I am looking for some custom authorization lets say "Can access only if age is 18+", so how to implement same in RazorPages (Not MVC)?
Is there any neat and clear solution for this??
Currently it is not possible to use Authorize attribute on individual Razor Page handlers , you can trace the feature request in here .
As a workaround , you can inject the IAuthorizationService and implement the authorization manually :
private readonly IAuthorizationService _authorizationService;
public IndexModel(ILogger<PrivacyModel> logger, IAuthorizationService authorizationService)
{
_logger = logger;
_authorizationService = authorizationService;
}
public async Task OnGet()
{
var isAuthorized = await _authorizationService.AuthorizeAsync(
User, "PolicyName");
if (!isAuthorized.Succeeded)
{
//not pass the authorzation
}
}
And create policy to check whether the value user's age claim is over 18 as shown here .

Authorization policy via attribute before data binding in web api

I am struggling to find a good solution for doing custom authorization checks without having to repeat the authorization check manually over and over again.
To illustrate, suppose I have the following setup for a .net core web api, which has two endpoints, one for GET and one for POST. I would like to check (maybe against db) whether the user has the right to see the resource, or the right to create a resource.
This is what the documentation refers to as resource based authorization
and would look something like this:
[Authorize]
[ApiVersion ("1.0")]
[Route ("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class ResourcesController : ControllerBase {
private readonly IAuthorizationService _authorizationService;
//..constructor DI
[HttpGet ("{resourceId}")]
public ActionResult<Resource> Get (Guid resourceId) {
var authorizationCheck = await _authorizationService.AuthorizeAsync (User, resourceId, ServiceOperations.Read);
if (!authorizationCheck.Succeeded) {
return Forbid ();
}
return Ok (ResourceRep.Get (resourceId));
}
[HttpPost]
public ActionResult<Resource> Post ([FromBody] Resource resource) {
var authorizationCheck = await _authorizationService.AuthorizeAsync (User, null, ServiceOperations.Write);
if (!authorizationCheck.Succeeded) {
return Forbid ();
}
return Ok (ResourceRep.Create (resource));
}
}
Now imagine the ServiceOperations enum has a long list of supported operations, or there are 100 different endpoints, I will have to do the same check everywhere, or even worse, might forget to add a check where I should definitely have added a check. And there is not an easy way to pick this up in unit tests.
I thought of using attributes but as the docs state:
Attribute evaluation occurs before data binding and before execution of the page handler or action that loads the document. For these reasons, declarative authorization with an [Authorize] attribute doesn't suffice. Instead, you can invoke a custom authorization method—a style known as imperative authorization.
So it seems I cannot use an authorization policy and decorate the methods with authorization attributes (which are easy to unit test that they are there) when the check itself requires a parameter that is not available (the resourceId).
So for the question itself:
How do you use imperative (resource based) authorization generically without having to repeat yourself (which is error-prone). I would love to have an attribute like the following:
[HttpGet ("{resourceId}")]
[AuthorizeOperation(Operation = ServiceOperations.Read, Resource=resourceId)]
public ActionResult<Resource> Get (Guid resourceId) {..}
[AuthorizeOperation(Operation = ServiceOperations.Write)]
[HttpPost]
public ActionResult<Resource> Post ([FromBody] Resource resource) {..}
You can achieve it using AuthorizationHandler in a policy-based authorization and combine with an injected service specifically created to determine the Operation-Resources pairing.
To do it, first setup the policy in Startup.ConfigureServices :
services.AddAuthorization(options =>
{
options.AddPolicy("OperationResource", policy => policy.Requirements.Add( new OperationResourceRequirement() ));
});
services.AddScoped<IAuthorizationHandler, UserResourceHandler>();
services.AddScoped<IOperationResourceService, OperationResourceService>();
next create the OperationResourceHandler :
public class OperationResourceHandler: AuthorizationHandler<OperationResourceRequirement>
{
readonly IOperationResourceService _operationResourceService;
public OperationResourceHandler(IOperationResourceService o)
{
_operationResourceService = o;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext authHandlerContext, OperationResourceRequirement requirement)
{
if (context.Resource is AuthorizationFilterContext filterContext)
{
var area = (filterContext.RouteData.Values["area"] as string)?.ToLower();
var controller = (filterContext.RouteData.Values["controller"] as string)?.ToLower();
var action = (filterContext.RouteData.Values["action"] as string)?.ToLower();
var id = (filterContext.RouteData.Values["id"] as string)?.ToLower();
if (_operationResourceService.IsAuthorize(area, controller, action, id))
{
context.Succeed(requirement);
}
}
}
}
the OperationResourceRequirement can be an empty class:
public class OperationResourceRequirement : IAuthorizationRequirement { }
The trick is, rather than specify action's Operation in attribute, we specify it elsewhere such as in database, in appsettings.json, in some config file, or hardcoded.
Here's an example getting the Operation-Resource pair from config file:
public class OperationResourceService : IOperationResourceService
{
readonly IConfiguration _config;
readonly IHttpContextAccessor _accessor;
readonly UserManager<AppUser> _userManager;
public class OpeartionResourceService(IConfiguration c, IHttpContextAccessor a, UserManager<AppUser> u)
{
_config = c;
_accessor = a;
_userManager = u;
}
public bool IsAuthorize(string area, string controller, string action, string id)
{
var operationConfig = _config.GetValue<string>($"OperationSetting:{area}:{controller}:{action}"); //assuming we have the setting in appsettings.json
var appUser = await _userManager.GetUserAsync(_accessor.HttpContext.User);
//all of needed data are available now, do the logic of authorization
return result;
}
}
Please note that to make IHttpContextAccessor injectable, add services.AddHttpContextAccessor() in Startup.ConfigurationServices method body.
After all is done, use the policy on an action:
[HttpGet ("{resourceId}")]
[Authorize(Policy = "OperationResource")]
public ActionResult<Resource> Get (Guid resourceId) {..}
the authorize policy can be the same for every action.

Custom roles on an Authorize attribute in MVC4

Here is my code:
[HttpGet, Authorize(Roles = "Admin")]
public ActionResult ActivityLog()
{
'code to do stuff
return View(model);
}
It's pretty simple - if you are in the "Admin" role you can get into this action. However I have a custom ActionFilter that populates my IPrinciple with all the custom claims (I cant use ADFS to send the claims because I have ONE ADFS for multiple sites so my claims have to be for that specific site).
public class CustomFilter: ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.User.Identity.IsAuthenticated)
{
'go get custom claims
}
}
}
I tie the custom filter into the application from the Global.asax file
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CustomFilter());
}
The problem is since the Authorize attribute runs before my custom filter I don't have the
"Admin" role and i get a 401 - Unauthorized Access error. How do I still keep the filter AND use the "Roles" tag in the Authorize attribute?
In regards to
"The problem is since the Authorize attribute runs before my custom
filter I don't have the "Admin" role"
You can create a another Authorize attribute which access the claims first, and then your standard Authorization which sets up the Admin.
The way you do this is to register and specify the Order property
filters.Add(new AuthorizeAttribute(), 1);
filters.Add(new CustomAuthorizeAttribute(), 2);
See more information on Filter Ordering
AuthorizeAttribute is no longer supported in MVC 4. You should now use the new AllowAnonymous attribute.
http://blogs.msdn.com/b/rickandy/archive/2012/03/23/securing-your-asp-net-mvc-4-app-and-the-new-allowanonymous-attribute.aspx
ASP.NET MVC 4 includes the new AllowAnonymous attribute, you no longer need to write that code. Setting the AuthorizeAttribute globally in global.asax and then whitelisting (That is, explicitly decorating the method with the AllowAnonymous attribute) the methods you want to opt out of authorization is considered a best practice in securing your action methods.
If you attempt to use an action filter and override AuthorizeCore, you'll get a compile time error "There is no suitable method for override".
Here is another method of performing attribute authorization in MVC 4:
public class AuthAttribute : AuthorizeAttribute
{
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
HandleUnauthorizedRequest(actionContext);
}
protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
{
var response = actionContext.Request.CreateResponse(System.Net.HttpStatusCode.Redirect);
response.Headers.Add("Location", "http://www.google.com");
actionContext.Response = response;
}
}