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")]
Related
I created a simple API with .NET 5.0 with a single controller action.
This action is protected with a policy:
[ApiController]
public class EnterpriseController : Controller
{
[HttpGet]
[Authorize(Policy = "Read")]
[Route("customers")]
public IActionResult GetCustomersAsync()
{
return Ok("Second request!");
}
}
The policy is passed if the calling token of my API contains a role "Customers.Read.All". This is done with setting the RoleClaimType to "roles":
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
var existingOnTokenValidatedHandler = options.Events.OnTokenValidated;
options.Events.OnTokenValidated = async context =>
{
await existingOnTokenValidatedHandler(context);
options.Authority = "https://login.microsoftonline.com/common";
options.TokenValidationParameters.RoleClaimType = "roles";
};
});
Then I add the policy:
services.AddAuthorization(options =>
{
options.AddPolicy("Read", policy => policy.RequireRole("Customers.Read.All"));
});
If I call my controller with a valid token, the first request returns "Forbidden" as a HTTP result.
If I call a second or third time, the call works as expected.
My whole example can be found here.
If I remove the policy from the controller and leave only the [Authorize], a valid token is enough for the call. Now my first call is also passed on the first try.
So I am sure that my problem is about the policies...
Do you have any ideas, why my first call is always forbidden?
Update:
This is the output:
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.
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
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;
}
}
}
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");