I'm trying to find a solution for implementing custom System.Web.Mvc.AuthorizeAttribute by deriving from it and overriding some of its methods.
Every approach I'm trying, I'm facing with certain issues in the default authorization mechanism of the MVC 5 that prevents me from proper extending that.
I've done the huge research on this field on SO and many dedicated resources, but I couldn't get a solid solution for the scenario like my current one.
First limitation:
My authorization logic needs additional data like controller and method names and attributes applied to them rather than limited portion of the data HttpContextBase is able to provide.
Example:
public override void OnAuthorization(AuthorizationContext filterContext)
{
...
var actionDescriptor = filterContext.ActionDescriptor;
var currentAction = actionDescriptor.ActionName;
var currentController = actionDescriptor.ControllerDescriptor.ControllerName;
var hasHttpPostAttribute = actionDescriptor.GetCustomAttributes(typeof(HttpPostAttribute), true).Any();
var hasHttpGetAttribute = actionDescriptor.GetCustomAttributes(typeof(HttpGetAttribute), true).Any();
var isAuthorized = securitySettingsProvider.IsAuthorized(
currenPrincipal, currentAction, currentController, hasHttpPostAttribute, hasHttpGetAttribute);
...
}
This is why I can't implement my authorization logic inside the AuthorizeCore() method override since it gets only HttpContextBase as the parameter and what I need to make an authorization decision is AuthorizationContext.
This leads me to put my authorization logic to the OnAuthorization() method override as in the example above.
But here we come to the second limitation:
The AuthorizeCore() method is called by the caching system to make an authorization decision whether the current request should be served with the cached ActionResult or corresponding controller method should be used to create a new ActionResult.
So we can't just forget about the AuthorizeCore() and use the OnAuthorization() only.
And here we're returning to the initial point:
How to make authorization decision for the cache system based on the HttpContextBase only if we need more data from the AuthorizationContext?
with many subsequent questions like:
How are we supposed to properly implement the AuthorizeCore() in
this case?
Should I implement my own caching to let it supply
sufficient data to the authorization system? And how it can be done
if yes?
Or I should say good-bye to the caching for all controller
methods protected with my custom System.Web.Mvc.AuthorizeAttribute?
It must be said here that I'm going to use my custom System.Web.Mvc.AuthorizeAttribute as a global filter and this is
the complete good-bye to the caching if the answer to this
question is yes.
So the main question here:
What is the possible approaches around to deal with such custom authorization and proper caching?
UPDATE 1 (Additional information to address some possible answers around):
There is no gurantee in the MVC that every single instance of the
AuthorizeAttribute would serve single request. It can be reused
for many requests (see
here for more info):
Action filter attributes must be immutable, since they may be cached
by parts of the pipeline and reused. Depending on where this attribute
is declared in your application, this opens a timing attack, which a
malicious site visitor could then exploit to grant himself access to
any action he wishes.
In the other words, AuthorizeAttribute MUST be immutable and
MUST NOT share state between any method calls.
Moreover in the
AuthorizeAttribute-as-global-filter scenario, a single instance of
the AuthorizeAttribute is used to serve all request.
If you think that you save AuthorizationContext in the OnAuthorization() for a request, you're then able to get it in subsequent AuthorizeCore() for the same request, you're wrong.
As a result you would take authorization decision for the current request based on the AuthorizationContext from the other request.
If a AuthorizeCore() is triggered by the caching layer, OnAuthorization() has never called before for the current request (please refer the sources of the AuthorizeAttribute starting from CacheValidateHandler() down to AuthorizeCore()).
In the other words, if request is going to be served using the cached ActionResult, only the AuthorizeCore() would be called and not the OnAuthorization().
So you're unable to save AuthorizationContext anyway in this case.
Therefore, sharing the AuthorizationContext between the OnAuthorization() and AuthorizeCore() is not the option!
the OnAuthorization method is called before the AuthorizeCore method. So you can save the current context for later processing:
public class MyAttribute: AuthorizeAttribute
{
# Warning - this code doesn't work - see comments
private AuthorizationContext _currentContext;
public override void OnAuthorization(AuthorizationContext filterContext)
{
_currentContext = filterContext;
base.OnAuthorization(filterContext);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// use _currentContext
}
}
Edit
Since this will not work as Alexander pointed out. The second option could be to completely override the OnAuthorization method:
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (OutputCacheAttribute.IsChildActionCacheActive(filterContext))
{
throw new InvalidOperationException(MvcResources.AuthorizeAttribute_CannotUseWithinChildActionCache);
}
bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true);
if (skipAuthorization)
{
return;
}
if (AuthorizeCore(filterContext.HttpContext))
{
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
var actionDescriptor = filterContext.ActionDescriptor;
var currentAction = actionDescriptor.ActionName;
var currentController = actionDescriptor.ControllerDescriptor.ControllerName;
var hasHttpPostAttribute = actionDescriptor.GetCustomAttributes(typeof(HttpPostAttribute), true).Any();
var hasHttpGetAttribute = actionDescriptor.GetCustomAttributes(typeof(HttpGetAttribute), true).Any();
// fill the data parameter which is null by default
cachePolicy.AddValidationCallback(CacheValidateHandler, new { actionDescriptor : actionDescriptor, currentAction: currentAction, currentController: currentController, hasHttpPostAttribute : hasHttpPostAttribute, hasHttpGetAttribute: hasHttpGetAttribute });
}
else
{
HandleUnauthorizedRequest(filterContext);
}
}
private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
// the data will contain AuthorizationContext attributes
bool isAuthorized = myAuthorizationLogic(httpContext, data);
return (isAuthorized) ? HttpValidationStatus.Valid : httpValidationStatus.IgnoreThisRequest;
}
Related
As the title says i am trying to fetch the required param in my route:
https://localhost:44386/api/Users/c6b44b17-1b05-4ab6-a8c0-6968b4ea1ced
This is my Filter which i put a breakpoint to see what is the value of each.
Unfortunately the first two doesn't have anything even if i specify the key (e.g. Query["_id"]).
The third one fetches the array of route which is correct but i don't want to use the last index value as it feels to be just a hack.
I've seen the docs and i can't find a simpler way below without touching the endpoint routing config and explicitly defining this specific route (since i might end up with tons of routes which might require this same scenario).
Any help will be truly appreciated.
public class ProfileIsCreatorFilter : Attribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
var query = context.HttpContext.Request.Query;
var queryString = context.HttpContext.Request.QueryString;
var route = context.HttpContext.Request.RouteValues;
}
}
[HttpGet("{_id}"), Authorize, ProfileIsCreatorFilter]
public ActionResult<User> GetUser(Guid _id)
{
return Ok();
}
I have simplified everything to avoid confusion.
You can access all route specific values in the filter-context. The context contains a RouteData dictionary:
public override void OnActionExecuting(ActionExecutingContext context) {
string myvalue = context.RouteData.Values["mykey"];
}
You can find more infos to ActionFilters in the docs.
I have the following code:
CookieHeaderValue cookie = Request.Headers.GetCookies("session").FirstOrDefault();
var isAuthenticated = _userService.IsAuthenticated(cookie);
if (!isAuthenticated)
return Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "");
I'd like this code to execute as soon as any part of my api is called. I havn't found any good solutions or ways to do this so i thought i would ask here instead.
(what I do now is execute the code in every get/post/put/delete which is horrible).
The best place to solve this would be an authorization filter attribute. See Authentication Filters in ASP.NET Web API 2.
The subject is too broad to repeat here in its entirety, but it comes down to creating an attribute:
public class CookieAuthenticationFilterAttribute : Attribute, IAuthenticationFilter
{
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
// your cookie code
}
}
And applying it to the controller or action methods:
[YourCookieAuthentication]
But be sure to read the link.
You can use an ActionFilter or AuthorizationFilter for this purpose. These are attribute classes that you can use on specific controllers/actions or globally. So you don't need to repeat the code for every action.
See this link for details. It shows the general authentication/authorization flow in ASP.NET Web API and how you can customize it.
So i found the best solution for my problem was the following code:
public class CookieFilterAttribute : AuthorizeAttribute
{
[Inject]
public IUserService UserService { get; set; }
protected override bool IsAuthorized(HttpActionContext actionContext)
{
CookieHeaderValue cookie = actionContext.Request.Headers.GetCookies("session").FirstOrDefault();
var isAuthenticated = UserService.IsAuthenticated(cookie);
return isAuthenticated;
}
}
I have a custom filter:
public class SetAuthFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
//do something
}
}
In the Application_Start() under the Global.asax:
GlobalFilters.Filters.Add(new SetAuthFilter());
The above codes will be called everytime an action is invoked.
However, in my _Layout.cshtml, I have 2 different "BaseController", something like:
#Html.Action("SomeAction", "Base")
#Html.Action("SomeAction", "Base2")
When I set a break point, it appears that the SetAuthFilter is always being called three times, first before the page was launched, then second time when my break point hits the BaseController, then finally the third time when my break point hits the Base2Controller.
What should I do in order to avoid SetAuthFilter being called multiple times?
You simply cannot prevent it from being called if there are multiple actions that are interacting with the filter. It will be called every single request. However, you could cache your last request for that user's identity and, if it is the same request, immediately return without continuing onto the heavier authorization checks.
public class SetAuthFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
var key = CreateKey(filterContext);
var isCached = HttpRuntime.Cache.Get(key);
if (isCached != null) return;
HttpRuntime.Cache.Insert(key, true);
// Heavy auth logic here
}
private string CreateKey(AuthorizationContext context)
{
// Or create some other unique key that allows you to identify
// the same request
return
context.RequestContext.HttpContext.User.Identity.Name + "|" +
context.RequestContext.HttpContext.Request.Url.AbsoluteUri;
}
}
Note that this doesn't account for null identities or bad URIs. You'd want to add some additional defensive checks as well as a cache invalidation strategy.
This will not allow you to bypass your authentication, since each unique request will still need to be validated. However, it minimizes the number of times you call expensive authorization logic.
For every secure controller action, the OnAuthorization overload will get called.
If you dont want that to happen, you should decorate your function with AllowAnonymous attribute.
If you don't want to call custom filter on each method:
Then remove the following line from Application_Start() under the Global.asax:
GlobalFilters.Filters.Add(new SetAuthFilter());
Add [SetAuth] attribute as follows on those methods and Controllers which really needs authorization filter :
[SetAuth]
public ActionResult Index()
{
// your code
return View(yourModel);
}
In my filterConfig, I have registered a global attribute filter, which requires authorization on each of my methods.
However, I have one particular method where I want to apply a different authorization filter attribute. How does one accomplish this, if at all possible?
Note: I do not want to use the [AllowAnonymous] attribute (which works seamlessly and completely ignores my filter), since I want the request to be authorized, just through a different set of authorization logic on the method.
You can alter your filter to allow multiple by setting AllowMultiple = true in the AttributeUsage attribute on your attribute class, and add a check so that if the filter is present multiple times, the globally-applied one doesn't execute. The ActionExecutingContext that gets passed into OnActionExecuting() lets you get the filters applied via filterContext.ActionDescriptor.GetCustomAttributes(), so you can use that here.
Then, alter the constructor so that you can pass in a parameter (probably an enum) that it can use to decide which authorisation method to use - the normal one, or this other one. Give that parameter a default value that makes it select the normal auth method. Then, on that one method that needs a different auth method, you can apply the filter with the other value of the parameter. So it might look like this:
public class CustomAuthAttribute : AuthorizeAttribute
{
public CustomAuthAttribute(AuthMethod method = AuthMethod.StandardAuth)
{
//stuff
}
}
[CustomAuth(AuthMethod.WeirdAuth)]
public ActionResult MethodThatNeedsDifferentAuth()
{
//stuff
}
you can write your own version of the authorize attribute and pass specific parameter to depending on what action would you like your attribute to do for example
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public string currentAction { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (currentAction != "notallowed")
{
HandleUnauthorizedRequest(filterContext);
}
}
}
protected override void HandleUnauthorizedRequest(AuthorizationContext context)
{
context.Result = new RedirectResult("/home/login");
}
and then apply it to your class or action
[CustomAuthorize(currentAction = "notallowed")]
public class HomeController : Controller
For some reason, only the method OnAuthorization is being invoked, but AuthorizeCore not.
this is how I call it:
[AuthorizeWithRoles(Roles = "Affiliate")]
public string TestOnlyAffiliate()
{
return "ok";
}
this is the actual attribute.
public class AuthorizeWithRolesAttribute : AuthorizeAttribute
{
public string Roles { get; set; }
//
//AuthorizeCore - NOT INVOKING!
//
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return true;
}
public void OnAuthorization(AuthorizationContext filterContext)
{
}
}
You're not supposed to override OnAuthorization. It deals with potential caching issues and calls AuthorizeCore.
http://aspnetwebstack.codeplex.com/SourceControl/changeset/view/1acb241299a8#src/System.Web.Mvc/AuthorizeAttribute.cs
// In the worst case this could allow an authorized user
// to cause the page to be cached, then an unauthorized user would later be served the
// cached page.
Put your custom logic in AuthorizationCore.
Not sure if this helps you at all, but I ran into this same thing and determined that, at least for my purposes, I didn't need to override AuthorizeCore at all. I'm not sure why it's there, to be honest. As MSDN says, OnAuthorization is invoked "when a process requests authorization." This means that it will be invoked for any method that has your AuthorizeWithRoles attribute. You can put your custom code within OnAuthorization to check whether or not the user has permission, and since you can get the httpContext from filterContext, there's really no need for AuthorizeCore. Here's a simple example that works for me:
public class LoginRequired : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (Common.ValidateCurrentSession(filterContext.HttpContext))
{
//this is valid; keep going
return;
}
else
{
//this is not valid; redirect
filterContext.Result = new RedirectResult("/login");
}
}
}
I hope that helps. Besides that, obviously you'll need to declare that OnAuthorization is an override.
EDIT: I believe the base OnAuthorization method is what calls into AuthorizeCore. Since you're overriding OnAuthorization, obviously that call is lost. I believe overriding AuthorizeCore would only be relevant if you left OnAuthorization alone or if you called base.OnAuthorization(filterContext) within the overridden method.