I have a default CustomAuthorizeAttribute defined in my Web Api project.
config.Filters.Add(new CustomAuthorizeAttribute());
However I have a special Controller where I would like to use a SpecialAuthorizeAttribute.
[SpecialAuthorize]
public class MySpecialController : ApiController
In the Asp.Net vNext we have a new attribute to override the default filters, but how could I make it work in the Web Api 2?
Edit 1:
One possible (but not ideal) solution is make the CustomAuthorizeAttribute check if there's another AuthorizeAttribute in the scope of the Controller or Action. In my case I have only the SpecialAuthorizeAttribute so:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if (actionContext.ControllerContext.ControllerDescriptor.GetCustomAttributes<SpecialAuthorizeAttribute>().Any() || actionContext.ActionDescriptor.GetCustomAttributes<SpecialAuthorizeAttribute>().Any())
{
return;
}
base.OnAuthorization(actionContext);
}
public override System.Threading.Tasks.Task OnAuthorizationAsync(System.Web.Http.Controllers.HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken)
{
return base.OnAuthorizationAsync(actionContext, cancellationToken);
}
}
You can use OverrideAuthenticationAttribute.
As you can this answer this attribute is used to suppress global authentication filters.
OverrideAuthorization attribute is the exact fit (in ASP.NET Web API 2) for your requirement. You can find its usage and purpose in simple terms in this article.
Related
I'm exploring Minimal APIs in .Net 6, and trying to apply a custom Authorization Filter to the endpoint (via Attributes or Extensions).
But it seems to me, I am doing something wrong, or it's simply not designed to work in that way (and it's sad if so).
Couldn't find anything in the docs besides the default usage of [Authorize] attribute in Minimal APIs.
Here is the Filter
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomAuthorizeAttribute : Attribute, IAuthorizationFilter
{
//Checking tokens
}
And if I try to apply it at Controller level, it works fine
[CustomAuthorize]
public class CustomController : ControllerBase
{
//Necessary routing
}
But if I switch to Minimap APIs notation and try to use attributes
app.MapGet("/customEndpoint",
[CustomAuthorize] async ([FromServices] ICustomService customService, Guid id) =>
await customService.GetCustomStuff(id));
or even an extension method
app.MapGet("/customEndpoint",
async ([FromServices] ICustomService customService, Guid id) =>
await customService.GetCustomStuff(id)).WithMetadata(new CustomAuthorizeAttribute());
It just doesn't work. The filter doesn't even being constructed.
What did I miss or did wrong?
Thx in advance
You can write a custom authorization filter for Minimal API in .NET 6.0
Here is how I tend to approach it - by using Policy-based authorization in ASP.NET Core
Step 1: Create a Requirement
A requirement implements IAuthorizationRequirement
public class AdminRoleRequirement : IAuthorizationRequirement
{
public AdminRoleRequirement(string role) => Role = role;
public string Role { get; set; }
}
Note: A requirement doesn't need to have data or properties.
Step 2: Create a Requirement Handler
A requirement handler implements AuthorizationHandler<T>
public class AdminRoleRequirementHandler : AuthorizationHandler<AdminRoleRequirement>
{
public AdminRoleRequirementHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
{
if (context.User.HasClaim(c => c.Value == requirement.Role))
{
context.Succeed(requirement);
}
else
{
_httpContextAccessor.HttpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
_httpContextAccessor.HttpContext.Response.ContentType = "application/json";
await _httpContextAccessor.HttpContext.Response.WriteAsJsonAsync(new { StatusCode = StatusCodes.Status401Unauthorized, Message = "Unauthorized. Required admin role." });
await _httpContextAccessor.HttpContext.Response.CompleteAsync();
context.Fail();
}
}
private readonly IHttpContextAccessor _httpContextAccessor;
}
Note: HandleRequirementAsync method returns no value. The status of either success or failure is indicated by calling context.Succeed(IAuthorizationRequirement requirement) and passing the requirement that has been successfully validated or by calling context.Fail() to indicate AuthorizationHandlerContext.HasSucceeded will never return true, even if all requirements are met.
Step 3: Configure Your Policy in the Authorization Service
builder.Services.AddAuthorization(o =>
{
o.AddPolicy("AMIN", p => p.AddRequirements(new AdminRoleRequirement("AMIN")));
});
Step 4: Add Your Requirement Handler to DI
builder.Services.AddSingleton<IAuthorizationHandler, AdminRoleRequirementHandler>();
Step 5: Apply Policy to Endpoints
app.MapGet("/helloworld", () => "Hello World!").RequireAuthorization("AMIN");
I think you won't be able to inject action filter in minimal api, you can use 3 alternative approches.
Create a custom middleware and inject it in startup class, it would check every request and do the intended work as you filter is doing. You can put a check for the request path there if you only need to validate a specific controller/endpoint.
The second approach is you can inject httpcontext in minimal api like this, from that extract jwt token and validate that, if found not ok reject that request.
app.MapGet("/customEndpoint", async (HttpContext context, ICustomService service) =>
{
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
if (string.isNullOrEmpty(token) || <not a valid token>) return Results.Unauthorized();
// do some work
return Results.Ok(result);
});
as #Dai suggested, you can extract token in this way also
AuthenticationHeaderValue.TryParse(context.Request.Headers["Authorization"], out var parsed ) && parsed.Scheme == "BearerOrWhatever" ? parsed.Parameter : null
You can register the filter globally from startup.cs.
I have a custom authorize filter that called two times.
public sealed class SamaAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
//some code
base.OnAuthorization(filterContext);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//some logic code here
return true;
}
}
I just add it globally and not set it for any action.
i search it in all web but can not find good answer.
i used StructureMap library for dependency injection;
but i don't have any ioc filter registration.
even i used "AllowMultiple = false" but not worked for me.
the problem was an ajax call in my scripts.
I am using asp.net core mvc. Next to default authentification I have added very specific authorization which is done by using ResultFilterAttribute attribute.
In future, to make sure that developers are going to specify permissions for each controller method I would like to check if the attribute is set for method, before action is executed.
Can it be done in MVC middleware. Or maybe there is better approach?
Thanks to Christian for mentioning ControllerFactory. It was right approach in my case.
public class MyControllerFactory : DefaultControllerFactory
{
public MyControllerFactory (IControllerActivator controllerActivator, IEnumerable<IControllerPropertyActivator> propertyActivators)
: base(controllerActivator, propertyActivators)
{
}
public override object CreateController(ControllerContext context)
{
var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
var isDefined = controllerActionDescriptor.MethodInfo.GetCustomAttributes(inherit: true)
.Any(a => a.GetType().Equals(typeof(PermissionFilterAttribute)));
if (!isDefined)
{
throw new NotImplementedException();
}
return base.CreateController(context);
}
}
At at Startup.cs need to tell mvc to use MyControllerFactory when resolving IControllerFactory interface.
services.AddSingleton<IControllerFactory, MyControllerFactory>();
Following the docs here I tried to implement a policy-based auth scheme. http://docs.asp.net/en/latest/security/authorization/policies.html#security-authorization-handler-example
I ran into the issue that my Handle method was not being called on my custom AuthorizationHandler. (It does not throw here). It also does inject the dependency currently in the constructor.
Here it the AuthorizationHandler Code.
using WebAPIApplication.Services;
using Microsoft.AspNet.Authorization;
namespace WebAPIApplication.Auth
{
public class TokenAuthHandler : AuthorizationHandler<TokenRequirement>, IAuthorizationRequirement
{
private IAuthService _authService;
public TokenAuthHandler(IAuthService authService)
{
_authService = authService;
}
protected override void Handle(AuthorizationContext context, TokenRequirement requirement)
{
throw new Exception("Handle Reached");
}
}
public class TokenRequirement : IAuthorizationRequirement
{
public TokenRequirement()
{
}
}
}
In Start Up I have
// Authorization
services.AddSingleton<IAuthorizationHandler, TokenAuthHandler>()
.AddAuthorization(options =>
{
options.AddPolicy("ValidToken",
policy => policy.Requirements.Add(new TokenRequirement()));
});
The controller method is
// GET: api/values
[HttpGet, Authorize(Policy="ValidToken")]
public string Get()
{
return "test";
}
Hitting this endpoint returns nothing and there is a warning in the console of
warn: Microsoft.AspNet.Mvc.Controllers.ControllerActionInvoker[0]
Authorization failed for the request at filter 'Microsoft.AspNet.Mvc.Filters.AuthorizeFilter'.
I am able to hit other endpoints that don't have the attribute successfully.
SOS,
Jack
I'm putting this here for reference because I spent way too long figuring this out...
I had implemented a custom requirement and handler (empty for testing's sake):
using Microsoft.AspNetCore.Authorization;
using System.Threading.Tasks;
public class TestHandler : AuthorizationHandler<TestRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TestRequirement requirement)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
}
public class TestRequirement : IAuthorizationRequirement
{
}
Registered it in my Startup.cs ConfigureServices() section:
services.AddAuthorization(options =>
{
options.AddPolicy("Test", policy => policy.Requirements.Add(new TestRequirement()));
// Other policies here
}
Added it to my controller method:
[HttpGet]
[Authorize(Policy = "Test")]
public IActionResult Index()
{
Return View();
}
But was getting a 403 error (not 401) with every request to the controller method!
Turns out, I was not registering TestHandler with the ConfigureServices() (Dependency Injection) section of Startup.cs.
services.AddSingleton<IAuthorizationHandler, TestHandler>();
Hope this saves someone from banging their head on their desk. :|
The answer to this question is alluded to in a comment to adem caglin, so props to him.
The issue is that the AuthorizeFilter is rejecting the request before the AuthorizationHandler is being called. This is because for every use of the Authorize tag MVC adds AuthorizeFilter ahead of the AuthorizationHandler in the pipeline. This AuthorizeFilter checks to see if any of the current users identities are authorized. In my case there were no authorized identities associated with any user so this would always fail.
A solution (which IMO is somewhat hackish) is to insert a peice of middleware that will get executed before any MVC code. This middleware will add a generic authenticated identity to a User (if the user does not already have one).
Consequently the AuthorizeFilter check will pass and the Handle method on the AuthenticationHandler method will be executed and our problem will be solved. The middleware code (which needs to be added to Configure before app.UseMvc(); is called) is as follows
app.Use(async (context, next) =>
{
if (!context.User.Identities.Any(i => i.IsAuthenticated))
{
context.User = new ClaimsPrincipal(new GenericIdentity("Unknown"));
}
await next.Invoke();
});
An alternative way to override the AuthorizeFilter is outline here (Override global authorize filter in ASP.NET Core MVC 1.0)
Citing the response from here (Asp.Net Core policy based authorization ends with 401 Unauthorized)
Take a look at Asp.net Core Authorize Redirection Not Happening i think adding options.AutomaticChallenge = true; solves your problem.
We use an IAuthorizationFilter class to filter all request, and check if custom user claims are still present (multi-tenant app) in the authentication cookie. These information are essentials for the rest of the application. If these informations are not present, we redirect to the Login page.
public class TokenAuthorizationFilter : IAuthorizationFilter, IAsyncAuthorizationFilter
{
public TokenAuthorizationFilter()
{
// Some dependency injection ...
}
public void OnAuthorization(Microsoft.AspNet.Mvc.Filters.AuthorizationContext context)
{
CheckToken(context);
}
public Task OnAuthorizationAsync(Microsoft.AspNet.Mvc.Filters.AuthorizationContext context)
{
return CheckToken(context);
}
}
And we register our filter like this
services.AddMvc(config =>
{
config.Filters.Add(typeof(TokenAuthorizationFilter));
});
And the controller's action that I want to access is very simple :
[Authorize(Policy = "TokenValid")]
public class HomeController : AjaxBaseController
{
public IActionResult Index()
{
return View();
}
}
We even not reached the Policy of our AuthorizeAttribute. As I can see in the stacktrace, Identity is attempting to create a Microsoft.AspNet.Identity.SignInManager somewhere in the middleware after checking for a CookieAuthenticationOptions, I assumed that he's attempting to re-login the user, but it's not checking for my Filter ? Login is very special in our application, so I don't want to let Identity log automatically our user. I can reproduced this issue when the authentication cookie expired.
Any ideas ? Thanks !
You also need to make TokenAuthorizationFilter inherit from AuthorizeAttribute for an authorization filter, and rename it as TokenAuthorizationFilterAttribute.
This will become an attribute that you will be able to call with [TokenAuthorizationFilter]:
[TokenAuthorizationFilter]
public class HomeController : AjaxBaseController
{
public IActionResult Index()
{
return View();
}
}
Be careful when implementing both IAuthorizationFilter and IAsyncAuthorizationFilter, as ASP.NET Core will only call the async method in this case: if you do not need any async call, then only implement the IAuthorizationFilter interface.
Also, if you keep to register the filter like this:
services.AddMvc(config =>
{
config.Filters.Add(typeof(TokenAuthorizationFilter));
});
You will notice that the filter will be called for every action, as it will force the authorization filter to be called every time, so in this case you do not need to add the attribute on top of your action.
Finally I found out the problem. Every 30 minutes, Identity is trying to validate the user through SecurityStamp validation, and that's making the app crash because it needed a database connection which doesn't exists at the time of the validation. We've desactive this validation in our startup by reimplementing the OnValidatePrincipal :
options.Cookies.ApplicationCookie.Events = new CookieAuthenticationEvents { OnValidatePrincipal = (context) => Task.FromResult(0) };