I am using an authorization handler to put custom authorization in my controller in .net core. How can I get the parameters from the controller and use it to the authorization handler?
In the old .NET I can get the parameters from HttpContext request param like this:
var eventId = filterContext.RequestContext.HttpContext.Request.Params["id"];
I am not sure how can I achieved it in .net core
public class HasAdminRoleFromAnySiteRequirement : AuthorizationHandler<HasAdminRoleFromAnySiteRequirement>, IAuthorizationRequirement
{
public HasAdminRoleFromAnySiteRequirement()
{
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
HasAdminRoleFromAnySiteRequirement requirement)
{
//need to call get param from controller to used in the validation
// something like this
//var eventId = filterContext.RequestContext.HttpContext.Request.Params["id"];
// I tried the suggestion below but I can't get the parameter from routedata
// var mvcContext = context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext;
return Task.FromResult(0);
}
}
In ASP.NET Core 3.0 with endpoint routing enabled, you can get a route parameter value like this:
public class MyRequirementHandler : AuthorizationHandler<MyRequirement>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public MyRequirementHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, MyRequirement requirement)
{
var routeData = _httpContextAccessor.HttpContext.GetRouteData();
var areaName = routeData?.Values["area"]?.ToString();
var area = string.IsNullOrWhiteSpace(areaName) ? string.Empty : areaName;
var controllerName = routeData?.Values["controller"]?.ToString();
var controller = string.IsNullOrWhiteSpace(controllerName) ? string.Empty : controllerName;
var actionName = routeData?.Values["action"]?.ToString();
var action = string.IsNullOrWhiteSpace(actionName) ? string.Empty : actionName;
//...
}
}
In your handler you can do the following
var mvcContext = context.Resource as
Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext;
if (mvcContext != null)
{
// Examine MVC specific things like routing data.
}
If you want parameter values then the authorize attribute pieces run before binding has taking place. Instead you would move to an imperative call, inside your controller. This is basically resource based authorization, your parameter is a resource.
You would inject the authorization service into your controller;
public class DocumentController : Controller
{
IAuthorizationService _authorizationService;
public DocumentController(IAuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
}
Then write your handler slightly differently;
public class DocumentAuthorizationHandler : AuthorizationHandler<MyRequirement, Document>
{
public override Task HandleRequirementAsync(AuthorizationHandlerContext context,
MyRequirement requirement,
Document resource)
{
// Validate the requirement against the resource and identity.
return Task.CompletedTask;
}
}
You can see this handler takes a document, this can be whatever you like, be it an integer for an ID, or some type of view model.
Then you have access to it inside your HandleRequirementAsync() method.
Finally, you'd call it from within your controller, once binding has taken place;
if (await authorizationService.AuthorizeAsync(
User,
document,
yourRequirement))
{
}
In ASP.NET Core 2.2, you can get a route parameter value like this:
public class MyRequirementHandler : AuthorizationHandler<MyRequirement>
{
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, MyRequirement requirement)
{
var authContext = (AuthorizationFilterContext)context.Resource;
var routeValueOfX = authContext.HttpContext.GetRouteValue("X");
.
.
.
}
}
For future reference, starting .NET Core 5.0, the HttpContext is now passed instead, so you can do:
if (context.Resource is HttpContext httpContext)
{
var value = httpContext.GetRouteValue("key");
}
For .Net 5.0 (If you are using .NET 3.0, 3.1 then it will be better) use following:
public class MyAuthorizationPolicyHandler : AuthorizationHandler<OperationAuthorizationRequirement>
{
public MyAuthorizationPolicyHandler()
{
}
protected async override Task HandleRequirementAsync(AuthorizationHandlerContext context, OperationAuthorizationRequirement requirement)
{
var result = false;
if (context.Resource is Microsoft.AspNetCore.Http.DefaultHttpContext httpContext)
{
var endPoint = httpContext.GetEndpoint();
if (endPoint != null)
{
var attributeClaims = endPoint.Metadata.OfType<MyAuthorizeAttribute>()
//TODO: Add your logic here
}
if (result)
{
context.Succeed(requirement);
}
}
}
Please refer to following related discussion: "context.Resource as AuthorizationFilterContext" returning null in ASP.NET Core 3.0
You can access parameters directly from your handler pretty easily. Now, I'm sure if think works for earlier versions of core (you should update core anyway if you can), but in core 2.0 and beyond, you can cast the context.Resource to an AuthorizationFilterContext in the HandleRequirementAsync method like so
if(context.Resource is AuthorizationFilterContext mvcContext)
{
//do stuff to the mvcContext
}
Then, you can access the parameters like this
var value = mvcContext.HttpContext.Request.Query[key].FirstOrDefault();
where key is the parameter name you are looking for.
Or you could parse the query string like this
var queryString = mvcContext.HttpContext.Request.QueryString.ToString()
var foo = HttpUtility.ParseQueryString(queryString);
var value = foo[key]
again, where key is the parameter name you are looking for.
Related
I have a asp dotnetcore web service that exposes some endpoints. For some of the endpoints, I want to run a check if existing scorecard is visible. The endpoints urls are:
GET /api/v1/scorecard/{ScorecardId}/details
GET /api/v1/scorecard/{ScorecardId}/rankings
These endpoints are just examples but they could be tens in numbers. Each of these endpoints have their own handlers like:
public async Task<ScorecardDetails> Get(long scorecardId)
{}
public async Task<ScorecardRankings> Get(long scorecardId)
{}
In database, there is a table Scorecard that stores the scorecard details and has a column IsVisible. I want to return 404 for all calls to these scorecard endpoints for scorecards that are set IsVisible = False in database.
I think you should consider using IActionFilter and IAsyncActionFilter for this purpose. In there you have a chance to read the already bound-model for parameters to better validate it. Of course that way has its own complexity unless you accept the way in which we decorate every parameters on every methods that require to check the existence of objects. That way is fairly inconvenient but to make it convenient, you need to design a model to allow you to declare (setup or configure) your targeted endpoints as well as how to target the required parameters for the existence checking process.
Here I introduce the way of using a middleware, just like what you want originally. It sounds more convenient than using action filters but it has its own complexity and inconvenience. At the phase of the middleware, we don't have any data bound to parameters and even not any RouteData available yet. That means we need to parse for the route values (here only the object's id) from the path. Parsing is a complex job especially when we need to make it fast. However I think using Regex for this purpose here is acceptable (although the framework code does not seem to like using Regex for the best performance). The framework code has a much more strict requirement for performance because it's the platform we build everything on. But in your code, you can take tradeoff between performance and easy-to-implement.
First we need a custom middleware like this:
public class EnsureExistenceByIdFromRouteValuesMiddleware
{
readonly RequestDelegate _next;
readonly EnsureExistenceByIdFromRouteValuesOptions _options;
public EnsureExistenceByIdFromRouteValuesMiddleware(RequestDelegate next,
EnsureExistenceByIdFromRouteValuesOptions options)
{
_next = next;
_options = options;
}
public async Task Invoke(HttpContext context)
{
var serviceType = _options.ExistenceCheckingServiceType;
var routePatterns = _options.RoutePatterns;
if (serviceType != null && routePatterns != null && routePatterns.Count > 0)
{
var service = context.RequestServices.GetRequiredService(_options.ExistenceCheckingServiceType) as IExistenceCheckingService;
if (service != null)
{
var matchedRoute = routePatterns.Select(e => Regex.Match(context.Request.Path,
e ?? "",
RegexOptions.Compiled | RegexOptions.IgnoreCase,
TimeSpan.FromSeconds(3)))
.FirstOrDefault(e => e.Success);
var id = matchedRoute?.Groups?.Skip(1)?.FirstOrDefault()?.Value;
if (!string.IsNullOrEmpty(id))
{
var isExisted = await service.ExistsAsync(id);
if (!isExisted && !context.Response.HasStarted)
{
context.Response.StatusCode = 404;
if (!_options.LetMvcHandle404)
{
return;
}
}
}
}
}
await _next(context);
}
}
The associated options class:
public class EnsureExistenceByIdFromRouteValuesOptions
{
public IList<string> RoutePatterns { get; } = new List<string>();
public Type ExistenceCheckingServiceType { get; set; }
public bool LetMvcHandle404 { get; set; }
public EnsureExistenceByIdFromRouteValuesOptions AddRoutePattern(string pattern)
{
RoutePatterns.Add(pattern);
return this;
}
public EnsureExistenceByIdFromRouteValuesOptions ClearRoutePatterns()
{
RoutePatterns.Clear();
return this;
}
}
Your services (for checking object existence) should implement a common & well-known interface (used by the middleware) like this:
public interface IExistenceCheckingService
{
Task<bool> ExistsAsync(object id);
}
//this is a sample implementation (just for demo)
public class ExistenceCheckingService : IExistenceCheckingService
{
public Task<bool> ExistsAsync(object id)
{
//dummy implementation for testing, only id of 1 is existed.
return Task.FromResult(Equals(id, "1"));
}
}
We create a convenient extension class for using in Startup.ConfigureServices and Startup.Configure:
public static class EnsureExistenceByIdFromRouteValuesExtensions
{
public static IServiceCollection EnsureExistenceByIdFromRouteValues(this IServiceCollection services)
{
//configure the MvcOptions to add the custom middleware
return services.Configure<MvcOptions>(o => {
o.Filters.Add(new EnsureExistenceByIdFromRouteValuesActionFilter());
}).AddScoped<IExistenceCheckingService, ExistenceCheckingService>();
}
public static IApplicationBuilder UseEnsureExistenceByIdFromRouteValuesMiddleware(this IApplicationBuilder app,
EnsureExistenceByIdFromRouteValuesOptions options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
return app.UseMiddleware<EnsureExistenceByIdFromRouteValuesMiddleware>(options);
}
public static IApplicationBuilder UseEnsureExistenceByIdFromRouteValuesMiddleware(this IApplicationBuilder app,
Action<EnsureExistenceByIdFromRouteValuesOptions> configureOptions)
{
if (configureOptions == null) throw new ArgumentNullException(nameof(configureOptions));
var options = new EnsureExistenceByIdFromRouteValuesOptions();
configureOptions(options);
return app.UseEnsureExistenceByIdFromRouteValuesMiddleware(options);
}
//we use this filter for lately handling the 404 (set by the middleware)
class EnsureExistenceByIdFromRouteValuesActionFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context) {}
public void OnActionExecuting(ActionExecutingContext context)
{
if(context.HttpContext.Response.StatusCode == 404)
{
context.Result = new StatusCodeResult(404);
}
}
}
}
Use it in Startup.ConfigureServices:
services.EnsureExistenceByIdFromRouteValues();
Use it in Startup.Configure:
app.UseEnsureExistenceByIdFromRouteValuesMiddleware(o => {
//add your Regex patterns here
o.AddRoutePattern("/scorecard/(\\d+)/details/?$");
o.AddRoutePattern("/scorecard/(\\d+)/rankings/?$");
o.ExistenceCheckingServiceType = typeof(IExistenceCheckingService);
//setting this to true to not short-circuit right after the middleware
//the MVC middleware next will handle this (in the action filter)
//That way you will have a chance to use a custom view for 404
//(otherwise such as for Web APIs, we can let this be false as by default).
//o.LetMvcHandle404 = true;
});
NOTE: you need to know regex to use this. In the code above, I include just 2 sample regexes (matching your sample paths posted in your question). The regex patten must include one captured group for the object id (the (\\d+) in the sample patterns). That should be the first group (or should be the only group).
First you need to change the return type of the Get functions so that they can return a 404.
So:
public async Task<ScorecardDetails> Get(long scorecardId)
Becomes (pseudo-code):
public async Task<IActionResult> Get(long scorecardId) {
if(ScoreCardExists(scorecardId)) {
ScorecardDetails details = GetDetails(scorecardId);
return Ok(details);
}else{
return NotFound();
}
}
I'm creating a custom attribute in dotnet that is supposed to check the authorization header. If it is the same as some hard coded string it is supposed to pass but else the user should not be able to use the specified route.
I think I'm getting the response header correctly but I'm not sure how to send a HTTP response if it fails.
public class CustomAuthorization : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
var httpContext = context.HttpContext;
string authHeader = httpContext.Request.Headers["Authorization"];
if(authHeader == "Kawaii")
{
return;
//do nothing cause its fine
}
else
{
httpContext.Response.WriteAsync("The authorization header was incorrect, is should be Kawaii");
}
}
}
Any help would be greatly appreciated!
From what you've described, it sounds like you should be using OnActionExecuting instead of OnActionExecuted. Within the body, instead of writing to context.HttpContext.Response, you set context.Result to an ActionResult representing the response
public class CustomAuthorization : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
string authHeader = context.HttpContext.Request.Headers["Authorization"];
if(authHeader == "Kawaii")
return;
context.Result = new UnauthorizedResult();
}
}
However, this approach sounds like a better fit for an AuthorizationFilter instead of an ActionFilter. Have a look at the filter pipeline documentation for a list of the different types of filters and what they do.
I have an Asp.net core 2 Web api. and I'm trying to implement a custom authorisation filter.
At the moment I have the following:
public class AuthorisationAttribute : TypeFilterAttribute
{
public AuthorisationAttribute() : base(typeof(AuthorisationFilter))
{
Arguments = new object[] { new Claim(ClaimTypes.UserData, "will be my user data") };
}
}
public class AuthorisationFilter : IAuthorizationFilter
{
readonly HttpContext _httpContext;
public AuthorisationFilter(HttpContext httpContext)
{
_httpContext = httpContext;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var authorisationCookie = context.HttpContext.Request.Headers.Where(t => t.Key == "auth").FirstOrDefault();
var temp = new JwtSecurityTokenHandler();
var unencryptedToken = temp.ReadToken(authorisationCookie.Value) as JwtSecurityToken;
var session = _httpContext.Session;
//MORE TO DO HERE YET! Just want to test getting called when expected.
return;
}
}
Then on a controller method I Have:
public class HomeController : Controller
{
[Authorisation(),HttpGet]
public IActionResult Index()
{
return View("~/Views/Home/Index.cshtml");
}
}
When I run the application The authorisationAttribute constructor gets called. At the point I try to call the controller Method I receive the following Error:
InvalidOperationException: A suitable constructor for type
AuthorisationFilter; could not be located. Ensure the type is concrete
and services are registered for all parameters of a public
constructor.
So in my startup.cs file I also added:
services.AddScoped<IAuthorizationFilter, AuthorisationFilter>();
but it's made no difference
The built-in DI does not know anything about the current HttpContext, first you have to add the IHttpContextAccessor to the service collection:
services.AddHttpContextAccessor();
Then you can get it as your filters constructor argument:
public AuthorisationFilter(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
Then you can access to the current HttpContext via _httpContextAccessor.HttpContext.
However you can also access the current HttpContext through your AuthorizationFilterContext like you already use that in your sample code:
context.HttpContext
Edit: As you are setting the Argument property of the TypeFilterAttribute you have to make a constructor in your filter that uses that argument, so like:
public AuthorisationFilter(Claim claim)
{
}
I would like to require one policy for all actions on a controller, and I would like to also require a second policy for all calls to HTTP "edit methods" (POST, PUT, PATCH, and DELETE). That is, the edit methods should require both policies. Due to implementation requirements, and also a desire to keep the code DRY, I need the latter policy to be applied at the controller level, not duplicated on all the action methods.
As a simple example, I have a PeopleController, and I also have two permissions, implemented as Policies, ViewPeople and EditPeople. Right now I have:
[Authorize("ViewPeople")]
public class PeopleController : Controller { }
How do I go about adding the EditPeople policy/permission such that it "stacks" and only applies to the edit verbs?
I've run into two problems which both seem to be a real pain:
You can't have more than one AuthorizeAttribute or more than one Policy specified within the AuthorizeAttribute, AFAIK.
You can't access the Request in a custom AuthorizationHandler, so I can't check the HttpMethod to check it.
I tried working around the former with a custom Requirement and AuthorizationHandler, like so:
public class ViewEditRolesRequirement : IAuthorizationRequirement
{
public ViewEditRolesRequirement(Roles[] editRoles, Roles[] viewRoles)
=> (EditRoles, ViewRoles) = (editRoles, viewRoles);
public Roles[] EditRoles { get; }
public Roles[] ViewRoles { get; }
}
public class ViewEditRolesHandler : AuthorizationHandler<ViewEditRolesRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ViewEditRolesRequirement requirement)
{
if (context.User != null)
{
var canView = requirement.ViewRoles.Any(r => context.User.IsInRole(r.ToString()));
var canEdit = requirement.EditRoles.Any(r => context.User.IsInRole(r.ToString()));
if (context. // Wait, why can't I get to the bloody HttpRequest??
}
return Task.CompletedTask;
}
}
... but I got as far as if (context. before I realized that I didn't have access to the request object.
Is my only choice to override the OnActionExecuting method in the controller and do my authorization there? I assume that's frowned upon, at the very least?
You can't access the Request in a custom AuthorizationHandler, so I can't check the HttpMethod...
Actually, we can access the Request in an AuthorizationHandler. We do that by casting the context.Resource with the as keyword. Here is an example:
services.AddAuthorization(config =>
{
config.AddPolicy("View", p => p.RequireAssertion(context =>
{
var filterContext = context.Resource as AuthorizationFilterContext;
var httpMethod = filterContext.HttpContext.Request.Method;
// add conditional authorization here
return true;
}));
config.AddPolicy("Edit", p => p.RequireAssertion(context =>
{
var filterContext = context.Resource as AuthorizationFilterContext;
var httpMethod = filterContext.HttpContext.Request.Method;
// add conditional authorization here
return true;
}));
});
You can't have more than one AuthorizeAttribute....
Actually, we can have more than one AuthorizeAttribute. Note from the docs that the attribute has AllowMultiple=true. That allows us to "stack" them. Here is an example:
[Authorize(Policy="View")]
[Authorize(Policy="Edit")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
...
}
You can have an IHttpContextAccessor injected into your handler and use it in HandleRequirementAsync:
public class ViewEditRolesHandler : AuthorizationHandler<ViewEditRolesRequirement>
{
private readonly IHttpContextAccessor _contextAccessor;
public ViewEditRolesHandler(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ViewEditRolesRequirement requirement)
{
if (context.User != null)
{
var canView = requirement.ViewRoles.Any(r => context.User.IsInRole(r.ToString()));
var canEdit = requirement.EditRoles.Any(r => context.User.IsInRole(r.ToString()));
if (_contextAccessor.HttpContext.Request. // Now you have it!
}
return Task.CompletedTask;
}
}
As I am working on Asp.Net core Authorization part, I needed a new property in AuthorizeAttribute which I want to utilize as a extra permission value. So, I have extended the AuthorizeAttribute in my own custom Authorize attribute. See below:
public class RoleAuthorizeAttribute : Microsoft.AspNetCore.Authorization.AuthorizeAttribute
{
public string Permission { get; private set; }
public RoleAuthorizeAttribute(string policy, string permission) : base(policy)
{
this.Permission = permission;
}
}
Then, I've created an AuthorizationHandler to check for the requirement as below:
public class RolePermissionAccessRequirement : AuthorizationHandler<RolePermissionDb>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RolePermissionDb requirement)
{
// check here..
context.Succeed(requirement);
return Task.FromResult(0);
}
}
All respective service collection mapping I have already done, just omitted here.
Now, I want my attribute to use like this on controller action level:
[RoleAuthorize("DefaultPolicy", "CustomPermission")]
public IActionResult List()
{
}
Would anybody suggest me how would I access the permission property value given on the top of Action method in the handler RolePermissionAccessRequirement ??
I want to perform some sort of access rule based on custom permission value given in the Authorize attribute on top of Action method.
Thanks in advance!
To parametrize a custom Authorize attribute, create an authorization filter implementing IAsyncAuthorizationFilter. Then wrap the filter in a TypeFilterAttribute-derived attribute. This attribute can accept parameters and pass it to the authorization filter's constructor.
Usage example:
[AuthorizePermission(Permission.Foo, Permission.Bar)]
public IActionResult Index()
{
return View();
}
Implementation:
public class AuthorizePermissionAttribute : TypeFilterAttribute
{
public AuthorizePermissionAttribute(params Permission[] permissions)
: base(typeof(PermissionFilter))
{
Arguments = new[] { new PermissionRequirement(permissions) };
Order = Int32.MinValue;
}
}
public class PermissionFilter : Attribute, IAsyncAuthorizationFilter
{
private readonly IAuthorizationService _authService;
private readonly PermissionRequirement _requirement;
public PermissionFilter(
IAuthorizationService authService,
PermissionRequirement requirement)
{
//you can inject dependencies via DI
_authService = authService;
//the requirement contains permissions you set in attribute above
//for example: Permission.Foo, Permission.Bar
_requirement = requirement;
}
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
bool ok = await _authService.AuthorizeAsync(
context.HttpContext.User, null, _requirement);
if (!ok) context.Result = new ChallengeResult();
}
}
In addition, register a PermissionHandler in DI to handle PermissionRequirement with permission list:
public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
Look at this this GitHub project for a complete example.