Catching a failed authorization policy - c#

In .Net Core 2, suppose there is an authorization policy that checks that specific claims exist for the user:
public class CompletedProfileRequirement : AuthorizationHandler<CompletedProfileRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CompletedProfileRequirement requirement)
{
// Check that all required claims exist
if (/* invalid user claims */) {
context.Fail();
}
context.Succeed(requirement);
return Task.FromResult(0);
}
}
And we stick the policy check on top of an MVC controller:
[Authorize(Policy = "CompletedProfile")]
public class HomeController : Controller
{
// Controller stuff
}
How can I write an IAuthorizationFilter so that we can catch a failure on this particular CompletedProfileRequirement policy so that we can redirect the user to a page where they may complete their profile?

According to the authorization documentation, it's possible to access the MVC context from your AuthorizationHandler:
Frameworks such as MVC or Jabbr are free to add any object to the Resource property on the AuthorizationHandlerContext to pass extra information.
For example, MVC passes an instance of AuthorizationFilterContext in the Resource property. This property provides access to HttpContext, RouteData, and everything else provided by MVC and Razor Pages.
Therefore, you can do something like this:
public class CompletedProfileRequirement : AuthorizationHandler<CompletedProfileRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CompletedProfileRequirement requirement)
{
// Check that all required claims exist
if (/* invalid user claims */) {
// retrieve MVC context
if (context.Resource is AuthorizationFilterContext mvcContext)
{
// we still need to mark the requirement as succeeded,
// otherwise responde code is 401 unauthorized
context.Succeed(requirement);
// HTTP 302 temporary redirect to your page
mvcContext.HttpContext.Response.Redirect("/Home/Wherever", false);
return Task.FromResult(0);
}
}
context.Succeed(requirement);
return Task.FromResult(0);
}
}

You should add your policy with requirments to authorization on application startup, like:
services.AddAuthorization(options =>
{
options.AddPolicy("CompletedProfile", builder =>
{
builder.AddRequirements(new CompletedProfileRequirement());
});
});
And register handlers, like:
services.AddScoped<IAuthorizationHandler, CompletedProfileRequirementHandler>();
UPD:
For redirecting you should inject and use in controller:
IAuthorizationService authorizationService
And invoke:
var result = _authorizationService.AuthorizeAsync(User, someObject, "CompletedProfile");
if (!result.Succeeded)
return RedirectToAction("CompleteProfileAction");

Related

Extend Authorize attribute .Net Web API

A lot of my methods in my WebAPI looks like this. I have an authorization-filter on policy, but then i also need to validate my token and that the user provided in the token exists in DB. I am wondering if there is a way of extending the Authorize attribute so that after policy is validated, the token gets validated too. And is it also possible to return the user into the method or context if existing?
[Authorize(Policy = "Admin")]
[Route("get")]
[HttpGet]
public async Task<IActionResult> Get()
{
var (success, user) = await UserHelpers.ValidateTokenAndGetUserAsync(HttpContext, _userManager, _configuration, _logger, ControllerContext, ModelState);
if (!success || user?.CustomerId == null) return Unauthorized(HttpResponseHelper.GetErrorResponse(StatusCodes.Status401Unauthorized, _localizer[ResourceConstants.Unauthorized.UserNotFoundError].Value));
//Code
await UserHelpers.CookieToResponse(user, _configuration, _webHostEnvironment, Response, Request).ConfigureAwait(false);
return Ok();
}
First, I should address that you shouldn't customize Authorize attribute.
Using (Policy, Claim, Requirement) can help you to customize your authorization based on your business.
Then my question is that your user was in the database while issuing the token. So how can it be removed now? (The user in the JWT-Token is being validated via the signature of that)
If you want to double-check if the user is enabled, or not archived yet you can write another filter like this:
public class ApiController : Controller
{
[Authorize(Policy = "Bla")]
[CheckUserIsEnabled]
public async Task<IActionResult> Get()
{
return Ok();
}
}
public class CheckUserIsEnabledAttribute : Attribute, IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var userId = context.HttpContext.User.FindFirst(x => x.Type == System.Security.Claims.ClaimTypes.NameIdentifier);
if (userId is null)
{
context.Result = new UnauthorizedResult();
return;
}
bool exists = true;
//exists = db.CheckIfUserExistsAsync(userId);
if (!exists)
{
context.Result = new UnauthorizedResult();
return;
}
await next();
}
}
This solution works because the JWT claims will be added to current user claims on httpRequest instantiation (even if you don't put Authorize attribute on the corresponding action)

How does a custom authorization filter handle roles?

I have created a custom authorization filter in ASP.NET Core 2.2 MVC in order to handle regular and AJAX requests, and to redirect to a custom URL if user is not authorized.
On some of my controller actions, I have the filter set [CustomAuthorize(Roles = "ExampleRole")]. Since I made a custom authorization filter, I thought I would need to also write the logic to check the role claims. However, CustomAuthorize filter is able to correctly handle roles without any additional code.
How is this happening? Is it the additional code inherited from AuthorizeAttribute class that continues to run after the custom OnAuthorization method runs?
Code for custom authorization filter:
public class CustomAuthorize : AuthorizeAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
string redirectUrl = "/Auth/Login";
if (context.HttpContext.User.Identity.IsAuthenticated == false)
{
if (context.HttpContext.Request.IsAjaxRequest())
{
context.HttpContext.Response.StatusCode = 401;
//result is returned to AJAX call and user is redirected to sign in page
JsonResult jsonResult = new JsonResult(new { message = "Unauthorized", redirectUrl = redirectUrl });
context.Result = jsonResult;
}
else
{
context.Result = new RedirectResult(redirectUrl);
}
}
}
}
When you inherit AuthorizeAttribute, you will naturally overwrite the IAuthorizeData interface. When the project starts, the attributes that implement the IAuthorizeData interface will be converted into the corresponding filter through AuthorizationApplicationModelProvider. About 2.x, he conversion process can be viewed in this code https://github.com/dotnet/aspnetcore/blob/2.1.3/src/Mvc/Mvc.Core/src/ApplicationModels/AuthorizationApplicationModelProvider.cs.
Then, its authorization rules are passed to the filter.

Deciding to use Authorization or not on a per-request basis in Asp.net Core

I'm developing an ASP.net core MVC 2.2 app and at the moment I'm using the classic [Authorize] and [AllowAnonymous] attributes on my controller actions to decide whether a certain action can be accessed anonymously or requires authorization.
However, I have a requirement where certain actions should require authorization only if a certain header is missing from the http request. Usually I would implement this by simply having something like this:
[HttpGet]
[AllowAnonymous]
public IActionResult SomeAction()
{
if (!Request.Headers.ContainsKey("something"))
{
return RedirectToAction("SomeActionWithAuth");
}
...
}
[HttpGet]
[Authorize]
public IActionResult SomeActionWithAuth()
{
...
}
however, in this particular case I have a strict requirement to avoid redirects, so I cannot use this approach.
So my question is:
Is there a way to intercept the request before it reaches the controller and decide at runtime, on a per-request basis, if the request should require authentication or not?
If that is not possible, is there maybe a way to decide at runtime which controller/action to route the request to? (that way I could have a setup similar to the one above, with two actions with different auth requirements, but without causing an actual HTTP Redirect on the client side, which I cannot do)
Create your own authorisation policy to handle this.
public class HeaderRequirement : IAuthorizationRequirement
{
public HeaderRequirement(string header)
{
Header = header;
}
public string Header { get; }
}
public class HeaderRequirementHandler : AuthorizationHandler<HeaderRequirement>
{
protected override Task HeaderRequirementHandler (
AuthorizationHandlerContext context,
HeaderRequirement requirement)
{
var hasHeader = context.Request.Headers.ContainsKey(requirement.Header);
if (hasHeader) // if we have the header
{
context.Succeed(requirement); // authorization successful
}
return Task.CompletedTask;
}
}
Register the handler as a service
services.AddScoped<IAuthorizationHandler, HeaderRequirementHandler>();
Add the policy to the authorisation services
services.AddAuthorization(options =>
{
options.AddPolicy("SomeHeader", policy =>
policy.Requirements.Add(new HeaderRequirement("SomeHeader")));
});
Now you can use it like this: [Authorize(Policy = "SomeHeader")]
If you need it a little bit more dynamic, If you don't want to register every single header that could possible be authenticated but rather interpreted at run-time. You can write you own policy provider

Authorization in ASP .NET Core Razor pages

I am unable to implement policy-based authorization in ASP .NET Core for an action on a razor page.
I read through this comprehensive document on authorization and used its examples as guidence.
Razor page action code:
[Authorize(Policy = "test")]
public async Task<IActionResult> OnGetCreateAsync(string id)
Code in service configuration:
_ = services.AddAuthorization(options => {
options.AddPolicy("test", policy =>
policy.RequireAssertion(context =>
false));
});
I expect that if I call the action or endpoint service, e.g.
GET /Account?handler=Create
then the request will be denied with a 403 status response because the "test" policy states that everyone is unauthorized. However, in actual practice, the action is successfully called.
Razor Pages doesn't support [Authorize] at the handler level. i.e. You can only authorise a page as a whole, on the PageModel itself, as noted in the docs:
Policies can not be applied at the Razor Page handler level, they must be applied to the Page.
If authorising the page as a whole isn't a workable solution, you might need to move your OnGetCreateAsync handler into a controller/action pair, which can be attributed with [Authorize] accordingly.
There's also a related GitHub issue in the docs for this:
The [Authorize] filter attribute has been supported since 2.0 in Razor Pages, but note that it works at the page model class level
If you need a better workaround, see akbar's answer and Jim Yabro's answer.
Another solution for it is to check authentication by if clause.like this:
if (!HttpContext.User.Identity.IsAuthenticated)
{
return Redirect("/Front/Index");
}
and you can check roles in it too by finding role:
var user = await _userManager.FindByEmailAsync(model.Email);
var roles = await _userManager.GetRolesAsync(user);
I would recommend following Razor Pages authorization conventions in ASP.NET Core that looks like this:
services.AddRazorPages(options =>
{
options.Conventions.AuthorizePage("/Contact");
options.Conventions.AuthorizeFolder("/Private");
options.Conventions.AllowAnonymousToPage("/Private/PublicPage");
options.Conventions.AllowAnonymousToFolder("/Private/PublicPages");
});
In your case with policy test it would look like this:
options.Conventions.AuthorizePage("/Account", "test");
Source:
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/razor-pages-authorization?view=aspnetcore-5.0
Authorize attribute is supported but only on PageModel like this:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace PageFilter.Pages
{
[Authorize]
public class ModelWithAuthFilterModel : PageModel
{
public IActionResult OnGet() => Page();
}
}
Source:
https://learn.microsoft.com/en-us/aspnet/core/razor-pages/filter?view=aspnetcore-5.0#authorize-filter-attribute
Don't use the AuthorizeAttribute since it's unsupported.
Instead, once you have configured your policies in Startup.cs, you will be able to check those policies from within the page handlers.
Inject IAuthorizationService into your page model constructor
Call AuthorizeAsync() from within the handler.
Run a conditional check on the result's .Succeeded property.
If .Succeeded is false, return a Forbid() result.
This has nearly the same result as the [Authorize(Policy=...)] but executes later in the page lifecycle.
using Microsoft.AspNetCore.Authorization;
// ...
public class TestPageModel : PageModel {
readonly IAuthorizationService AuthorizationService;
public TestPageModel(IAuthorizationService authorizationService) {
AuthorizationService= authorizationService;
}
// Everyone can see this handler.
public void OnGet() { }
// Everyone can access this handler, but will be rejected after the check.
public async Task<IActionResult> OnPostAsync() {
// This is your policy you've defined in Startup.cs
var policyCheck = await AuthorizationService.AuthorizeAsync(User, "test");
// Check the result, and return a forbid result to the user if failed.
if (!policyCheck.Succeeded) {
return Forbid();
}
// ...
return Page(); // Or RedirectToPage etc
}
}
my workaround using Permission-based Authorization in ASP.NET Core :
[Authorize(Permissions.PageX.AddParameter)]
public async Task<IActionResult> OnPostAddParameterAsync(uint id, string key, string value)
{
if (!this.ArePermissionsValid()) return Forbid();
/// ...
}
public static class PageExtensions
{
public static bool ArePermissionsValid(this PageModel page)
{
try
{
var authorizeAttribute = new StackTrace().GetFrames().FirstOrDefault(x => x.GetMethod().Name.StartsWith("On")).GetMethod().GetCustomAttribute<AuthorizeAttribute>();
if (authorizeAttribute == null) return true;
var hasPermissions = page.User.Claims.Any(x => x.Type.Equals("permission") && x.Value.Equals(authorizeAttribute.Policy));
return hasPermissions;
}
catch (Exception e)
{
Log.Error($"${nameof(PageExtensions)}.{nameof(ArePermissionsValid)} | {e.Message}");
return false;
}
}
}

The AuthorizationPolicy named: 'Admin' was not found

I am learning Authentication/Authorization in .NET Core MVC.
I am trying to create a controller that can only be accessed by 'Admin', but get the following error.
An unhandled exception occurred while processing the request.
InvalidOperationException: The AuthorizationPolicy named: 'Admin' was
not found.
Here's my code. What should I do?
[HttpGet("~/Test")]
[Authorize("Admin")]
public async Task<string> MyMethod()
{
return await Task<string>.Run(() => "Hello Admin");
}
you can define the matching policy in Startup.cs
services.AddAuthorization(options =>
{
options.AddPolicy("Admin",
authBuilder =>
{
authBuilder.RequireRole("Administrators");
});
});
the authBuilder has other methods on it, you can require claims or specific user names or custom rules using policy based authorization and control the rules from a central place in Startup
https://docs.asp.net/en/latest/security/authorization/policies.html
In line with the documentation here, you have not added the Authorization attribute correctly. This is the correct way to do it.
[HttpGet("~/Test")]
[Authorize(Roles ="Admin")]
public async Task<string> MyMethod()
{
return await Task<string>.Run(() => "Hello Admin");
}
If you are using a Scheme-based authentication then you can pass the "AuthenticationSchemes "with authorize filter.
For example, [Authorize(AuthenticationSchemes = "UserBasicAuth")]
Here, your Startup.cs class should look like : AddScheme<AuthenticationSchemeOptions, ....>("UserBasicAuth", null)
Instead of, [Authorize(Policy = "UserBasicAuth")]

Categories