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.
Related
I am new the API in general, let me give you the background of the API and what I want it to do.
I have a API have that are external facing and so every incoming request are required to check the signature from header. literality my code in every controller call are checking the signature and created many duplicated code.
my question is how can reduces those duplicated code ? do I use Custom Attributes, or AuthorizeAttribute
here are some of the example code:
[Route("[controller]")]
[ApiController]
public class ExampleController : ControllerBase
{
public async Task<Result> Call_1(Rquest request)
{
string signaturel;
signature = Util.getHeaderSignature(request);
if(unit.IsSinatureValid(signaturel, someVar1, someVar2))
{
(My logic)
}
else{ return "InvalidSinaturemessage" }
}
public async Task<Result> Call_2(Rquest request)
{
string signaturel;
signature = Util.getHeaderSignature(request);
if(unit.IsSinatureValid(signaturel, someVar1, someVar2))
{
(My logic)
}
else{ return "InvalidSinaturemessage" }
}
}
above code is just for showing, the actual Sinature checking logic is around 20 lines of code on every single controller method.
Yes, you can do that using action filters. It's described in documentation
Put your code for checking into OnActionExecuting method. So, you can write Result in the action filter if the signature isn't valid.
In case you need specific result structure you can create your own ObjectResult:
public class ForbiddenObjectResult : ObjectResult
{
public string Message { get; private set; }
public ForbiddenObjectResult(object value, string message)
: base(value)
{
StatusCode = StatusCodes.Status403Forbidden;
Message = message;
}
}
...
string signaturel;
signature = Util.getHeaderSignature(context.HttpContext.Request);
if(!unit.IsSinatureValid(signaturel, someVar1, someVar2))
{
context.Result = new ForbiddenObjectResult(filterContext.ModelState, "InvalidSinaturemessage");
}
And to register it for all your endpoints(if needed):
services.AddControllersWithViews(options =>
{
options.Filters.Add<YourActionFilter>();
});
You can use token based authentication or filter method. For reference
Token based authentication
Custom Filter
I have created the web api with simple autorization via autorization requirments. My requirments code looks like:
public class TestRequirement : IAuthorizationRequirement { }
public class TestHandler : AuthorizationHandler<TestRequirement> {
protected override Task
HandleRequirementAsync(AuthorizationHandlerContext context, TestRequirement requirement) {
//context.Succeed(requirement); --#1
//context.Fail(); --#2
/*if (context.Resource is AuthorizationFilterContext mvcContext) {--#3
mvcContext.Result = new UnauthorizedResult();
}*/
return Task.CompletedTask;
}
}
Also, I updated Startup.ConfigureServices(...):
services.AddAuthorization(o => o.AddPolicy("Test", p => p.Requirements.Add(new TestRequirement())));
services.AddSingleton<IAuthorizationHandler, TestHandler>();
And I added apropriate attribute to controller: [Authorize(Policy = "Test")]
If I uncomment block #1 - it works as expected (I get my data). But when my code fails requiremnt (I comment #1), I get 500 Internal Server Error.
Then, I tried to explicit fail the requirment (uncomment block #2) - same result. I know it isn't recommended but I wanted to try.
After this, I tried the more ugly workaround, I commented #2 and uncommented block #3. I got same 500 status code.
Just for fun, I implemented resources filter with the same behavior:
public class TestResourceFilterAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context) {
context.Result = new UnauthorizedResult();
}
public void OnResourceExecuted(ResourceExecutedContext context) {
}
}
Then, I replaced on controller my authorize attribute with [TestResourceFilter] and got 401 Unauthorized as expected. But it the bad way to use resource filters.
What is wrong with my requirement implementation? And why I get 500 instead of 401 (or 403)?
EDIT: I found InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found. in my log.
I saw samples with cookies scheme, but it isn't suitable for me. Because I want to implement stateless calls.
poke's commnets pointed me that I implemented my functionality in wrong way. I tried to handle the security checking on authorization level, but I had to do it on authentication level. So my final code looks like:
public class TestHandlerOptions : AuthenticationSchemeOptions { }
internal class TestHandler : AuthenticationHandler<TestHandlerOptions> {
protected override async Task<AuthenticateResult> HandleAuthenticateAsync() {
if (await SomeCheckAsync()) {
var identity = new ClaimsIdentity(ClaimsName);
var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), null, ClaimsName);
return AuthenticateResult.Success(ticket);
}
return AuthenticateResult.Fail("Missing or malformed 'Authorization' header.");
}
}
Add next in ConfigureServices in Startup class:
services.AddAuthentication(options => options.AddScheme(SchemeName, o => o.HandlerType = typeof(TestHandler)));
And authorize attribute looks like [Authorize(AuthenticationSchemes = SchemeName)]
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.
I'm building the below filter:
public class TestflowFilter : FilterAttribute, IActionFilter
{
public void OnActionExecuted(ActionExecutedContext filterContext)
{
var profileId = int.Parse(ClaimsPrincipal.Current.GetClaimValue("UserId"));
var appId = int.Parse(filterContext.RouteData.Values["id"].ToString());
if (profileId != 0 && appId != 0)
{
if (CheckIfValid(profileId, appId))
{
// redirect
filterContext.Result = // url to go to
}
}
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
}
}
I actually only need OnActionExecuted, but since IActionFilter is an interface I have to implement them both. Is it ok to leave OnActionExecuting blank if I don't need anything to happen, or do I need to call a base version that MVC always runs?
Also in the OnActionExecuted method if the CheckIfValid is true I redirect the user, but if not I don't do anything. Is that ok or do I need to set some property on the filterContext instead.
I actually only need OnActionExecuted, but since IActionFilter is an interface I have to implement them both. Is it ok to leave OnActionExecuting blank if I don't need anything to happen, or do I need to call a base version that MVC always runs?
Leaving the method body empty is perfectly acceptable in this case. Looks good!
Also In the OnActionExecuted method if the CheckIfValid is true I redirect the user, but if not I don't do anything, is that ok or do I need to set some property on the filterContext instead.
Your filter is fine. MVC does offer a different abstract base class called ActionFilterAttribute, which implements these interfaces for you to override as needed. There's a nice overview that you can read about here. If you derive from that class, your filter attribute code could be simplified a little bit:
public class TestflowFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var profileId = int.Parse(ClaimsPrincipal.Current.GetClaimValue("UserId"));
var appId = int.Parse(filterContext.RouteData.Values["id"].ToString());
if (profileId != 0 && appId != 0)
{
if (CheckIfValid(profileId, appId))
{
// redirect
filterContext.Result = // url to go to
}
}
}
}
I am building a simple performance logger that hooks in to Application_EndRequest / Application_BeginRequest
I would like to send my logger the name of the action and controller as some sort of key.
How can I access this information? (Don't mind if I have to intercept it earlier on and keep it around in the context)
I know this is an old question, but you can access the requested information using:
HttpContext.Current.Request.RequestContext.RouteData.Values("controller")
HttpContext.Current.Request.RequestContext.RouteData.Values("action")
Not sure that you can.
I poked around the HttpContext.Current and found that on the second (and subsequent requests), the HttpContext.Current.Items collection contains an instance of a System.Web.Routing.UrlRoutingModule.RequestData class. Unfortunately, this class is private so you can't access its data. In the debugger, however, it seems that this contains the information you're looking for (not sure why it doesn't exist on the first request though).
Alternatively, could you just use an action filter and add that to a BaseController class that all of your controllers derive from? Something like:
public class LoggingActionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
var controllerName = filterContext.Controller.ControllerContext.RouteData.Values["controller"];
var actionName = filterContext.Controller.ControllerContext.RouteData.Values["action"];
}
}
Then create a base controller class with this attribute:
[LoggingAction]
public abstract class BaseController : Controller
{
}
This is working:
protected void Application_BeginRequest(object sender, EventArgs e)
{
var context = new HttpContextWrapper(HttpContext.Current);
var rd = RouteTable.Routes.GetRouteData(context);
// use rd
}
object GetControllerFromContext(HttpContext context) {
object controller = null;
HttpContextBase currentContext = new HttpContextWrapper(context);
UrlHelper urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext);
RouteData routeData = urlHelper.RouteCollection.GetRouteData(currentContext);
if(routeData != null) {
controller = routeData.Values["controller"];
}
return controller;
}