13
votes

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.

4
Can you show your Configure method in Startup? - Nemanja Todorovic
You also need to use UseAuthentication() middleware for these authorization policies to actually matter. - Vidmantas Blazevicius
Have you added UseAuthentication() middleware - Praneet Nadkar
Yes. I have added authentication middleware. - shertu

4 Answers

11
votes

Razor Pages doesn't currently 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.

3
votes

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);
0
votes

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.

  1. Inject IAuthorizationService into your page model constructor
  2. Call AuthorizeAsync() from within the handler.
  3. Run a conditional check on the result's .Succeeded property.
  4. 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
    }
}
0
votes

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://docs.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://docs.microsoft.com/en-us/aspnet/core/razor-pages/filter?view=aspnetcore-5.0#authorize-filter-attribute