Authorization in ASP .NET Core Razor pages - c#

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;
}
}
}

Related

Define validation on all controller routes

Let's say I create Admin Controller that includes all refers to administration pages.
In each controller i do validation that the user is admin and other validations,
Its look like my code repeats itself and i want to make local event on enter any route in admin controller and do these validations, I know i can declare one function that do these validations but again I must need to set her on any method in AdminController.
Thanks for helpers.
Example of admin controller method:
public IActionResult References()
{
var user = Systems.Users.GetUser(HttpContext);
if (Systems.Users.IsValid(user) &&
Systems.Users.IsPermission(user, Models.User.EUPermissions.Admin))
return View();
return View("Error404");
}
You can do the following in Startup.cs ConfigureServices() method:
services.AddAuthorization(options =>
{
options.AddPolicy("AdminAccess", policy => policy.RequireRole("Admin"));
});
Now in your controller, you can use the following:
[Authorize(Policy = "AdminAccess")]
public class AdministratorController : Controller
{
public IActionResult Index()
{
return View();
}
}
This way, you don't have to write code for checking access. .Net core promotes dependency injection and other solid principles and you don't have write methods which are doing more than one responsibility.

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

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")]

ASP.NET Web API Override authorization filter for a handler

How can I disable an authorization filter for a specific GET handler in Web API?
There's a custom authorization filter on the class level but for one of the methods I need to have no security. I tried applying [AllowAnonymous] attribute but it still runs through the higher-level filter and fails. That custom filter derives from AuthorizationFilterAttribute. The class also have two another attributes: OverrideAuthentication and EnableCors.
I tried AllowAnonymous attribute but it doesn't.
Sample code:
[EnableCors(origins: "*", headers: "*", methods: "*")]
[OverrideAuthentication]
[AccountAuthorization]
public class AccountsController : ApiController
{
[Route("api/accounts/{accountNumber}/GetX")]
[AllowAnonymous]
[HttpGet]
public HttpResponseMessage GetX(string accountNumber)
{
HttpResponseMessage response = null;
IEnumerable<string> apiKey;
if (!Request.Headers.TryGetValues("X-ApiKey", out apiKey) || apiKey.Count() != 1 || apiKey.First() != API_KEY)
{
throw new HttpResponseException(HttpStatusCode.Forbidden);
}
// Process
// ..
// ..
return response;
}
}
EDIT: The linked answer doesn't explain what's the solution.
Figured it out at last.
Since there is already an existing custom authorization filter on the class/controller level, therefore, to override a specific action handler (the method) and have it work without any authorization filters, we need to override the filter at the controller/class level. So adding the OverrideAuthorization filter did the trick. Now AllowAnonymous will be to do its magic.
[Route("api/accounts/{accountNumber}/GetX")]
[AllowAnonymous]
[OverrideAuthorization]
[HttpGet]
public HttpResponseMessage GetX(string accountNumber)
{
// Process
// ..
// ..
}

Use Authorize Attribute to Verify Identity User Logged In

I'm creating a new ASP.NET web application and I'm not planning on making use of the concept of "roles". I do, however, want to make sure a user is logged in on certain pages. Is there any existing attribute that simply checks if a user is logged in and redirects them or throws an error if they're not? Every search I've done points to using roles (such as this one).
The [Authorize] attribute will only return successfully if the user initiating the request is logged in and will only work on controllers and action methods.
It can be used to decorate a particular action:
public class FooController : Controller
{
// only FooAction requires authentication in FooController
[Authorize]
public async Task<ActionResult> FooAction()
{
}
public async Task<ActionResult> BarAction()
{
}
}
...or an entire controller:
// all actions in FooController require authentication
[Authorize]
public class FooController : Controller
{
public async Task<ActionResult> FooAction()
{
}
public async Task<ActionResult> BarAction()
{
}
}
You also have Request.IsAuthenticated which can be used on both action and non-action methods:
if (Request.IsAuthenticated) //or #if in Razor
{
//request is authenticated
}
...and even User.Identity.IsAuthenticated as #Darko correctly pointed out in his answer. Personally, I prefer Request.IsAuthenticated over User.Identity.IsAuthenticated as it also provides some useful null-checks for User and User.Identity. Here's how Request.IsAuthenticated looks under the hood:
public bool IsAuthenticated
{
get
{
return(_context.User != null
&& _context.User.Identity != null
&& _context.User.Identity.IsAuthenticated);
}
}
You can use User property, just put if() where it can control access and that's it.
protected void Page_Load(object sender, EventArgs e)
{
if (User.Identity.IsAuthenticated)
{
Page.Title = "Home page for " + User.Identity.Name;
}
else
{
Page.Title = "Home page for guest user.";
}
}
This should work after you set the web.config . Here is the documentation https://msdn.microsoft.com/en-us/library/9wff0kyh(v=vs.85).aspx

Categories