I see that version 2 of MVC.NET now has a RequireHttps attribute, which works great for me. However, what's a good strategy for turning the effect off? For example, I want to use Https on some pages, but regular Http on others. Should I create my own RequireHttp attribute?
EDIT: I'm using my own RequireHttp attribute, and it works fine, but I'm wondering if there's some built-in functionality in MVC.NET Version 2 that I'm missing.
EDIT 2: I must not have been clear. My question concerns the following: if you use RequireHttps, then any requests after that will be over Https even if the Controller or Action is not decorated with RequireHttps. Unless I'm mistaken, you need a 2nd attribute such as RequireHttp to redirect requests to Http instead of Https.
The point of the ActionFilterAttribute is that you can apply them to any actions you want. Or in other words, you don't have to apply them to all the actions.
If you don't want an attribute's logic injected into an action, then simply don't apply the attribute to it. For example :
public class SomeController : Controller {
[RequireHttps]
public ActionResult SomeAction() {
//the attribute's logic will be injected to this action.
return View();
}
public ActionResult SomeOtherAction() {
//this action doesn't require https protocol
return View();
}
}
If you apply the attribute to the controller itself, then it will be applied to all the actions in the controller.
Edit :
To require http protocol instead of https, I think you can use the attribute below. I will double check to see if MVC 2 has this already. But if it doesn't (It doesn't) :
public class RequireHttp : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext) {
if (filterContext.HttpContext.Request.IsSecureConnection) {
UriBuilder builder = new UriBuilder() {
Scheme = "http",
Host = filterContext.HttpContext.Request.Url.Host,
Path = filterContext.HttpContext.Request.RawUrl
};
filterContext.Result = new RedirectResult(builder.ToString());
filterContext.Result.ExecuteResult(filterContext);
}
base.OnActionExecuting(filterContext);
}
}
You could apply the attribute either at the controller in which case it will apply for all actions, or only on selected actions.
//apply to all actions
[RequireHttps]
public class SomeController
{
//apply to this action only
[RequireHttps]
public ActionResult SomeAction()
{
}
}
It worked for me!
I converted the çağdaş answer to Visual Basic:
Public Class RequireHttpAttribute
Inherits ActionFilterAttribute
Public Overrides Sub OnActionExecuting(ByVal filterContext As _
ActionExecutingContext)
If (filterContext.HttpContext.Request.IsSecureConnection) Then
Dim builder As UriBuilder = New UriBuilder()
builder.Scheme = "http"
builder.Host = filterContext.HttpContext.Request.Url.Host
builder.Path = filterContext.HttpContext.Request.RawUrl
filterContext.Result = New RedirectResult(builder.ToString())
filterContext.Result.ExecuteResult(filterContext)
End If
MyBase.OnActionExecuting(filterContext)
End Sub
End Class
I use it like this:
<RequireHttp()> _
Public Class SomeController
<RequireHttp()> _
Function SomeAction(...) As ActionResult
...
End Function
End Class
I'm also using Joel Mueller's RemoteRequireHttps described here:
ASP.NET MVC RequireHttps in Production Only
[RequireHttps] //apply to all actions in controller
public class SomeController
{
//... or ...
[RequireHttps] //apply to this action only
public ActionResult SomeAction()
{
}
}
Applying it at the controller level also applies it to all action methods, otherwise apply it to each individual Action method.
Related
I want to redirect the user to a different view if they are using a mobile browser. I've decided I'd like to do this using MVC filters by applying it to actions which I want to have a mobile view.
I believe this redirect needs to happen in OnActionExecuted, however the filterContext does not contain information on the view - it does, however in OnResultExecuted, but by this time I believe it is too late to change the view.
How can I intercept the view name and change the ViewResult?
This is what I have in the result executed and what I'd like to have work in Action Executed.
public class MobilePageFilter : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if(filterContext.Result is ViewResult)
{
if (isMobileSite(filterContext.HttpContext.Session[SetMobile.SESSION_USE_MOBILE]))
{
ViewResult viewResult = (ViewResult)filterContext.Result;
string viewName = viewResult.ViewName;
filterContext.Result = new ViewResult
{
ViewName = "Mobile/" + viewName,
ViewData = viewResult.ViewData,
TempData = viewResult.TempData
};
}
}
base.OnResultExecuted(filterContext);
}
}
I would recommend you the following blog post which explains a better alternative to achieve what you are asking for rather than using action filters.
This is what I ended up doing, and wrapped up into a reusable attribute and the great thing is it retains the original URL while redirecting (or applying whatever result you wish) based on your requirements:
public class AuthoriseSiteAccessAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
// Perform your condition, or straight result assignment here.
// For me I had to test the existance of a cookie.
if (yourConditionHere)
filterContext.Result = new SiteAccessDeniedResult();
}
}
public class SiteAccessDeniedResult : ViewResult
{
public SiteAccessDeniedResult()
{
ViewName = "~/Views/SiteAccess/Login.cshtml";
}
}
Then just add the attribute [SiteAccessAuthorise] to your controllers you wish to apply the authorisation access to (in my case) or add it to a BaseController. Make sure though the action you are redirecting to's underlying controller does not have the attribute though, or you'll be caught in an endless loop!
I would like to know how am I able to redirect the request inside the controller constructor if I need to do it?.
For example:
Inside the constructor I need to initialize an object with an dynamic value, in some cases I don't want to do it and in that case I want to redirect to some other place.
At the same way the rest of the constructor will not be executed neither the "original following action".
How can I do it?
Thank you
EDIT #1
Initially I used:
public override void OnActionExecuting(ActionExecutingContext filterContext)
There I could redirect to some other controller/action/url, but later in time, I needed to change my controller, where I initialize an variable in his constructor and have some code that really needs to redirect the request :P
I need this also because the OnActionExecuting executes AFTER the controller constructor.
And in my logic, the redirect needs to be done there.
Performing redirects inside the controller constructor is not a good practice because the context might not be initialized. The standard practice is to write a custom action attribute and override the OnActionExecuting method and perform the redirect inside. Example:
public class RedirectingActionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
if (someConditionIsMet)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new
{
controller = "someOther",
action = "someAction"
}));
}
}
}
and then decorate the controller which you would like to redirect with this attribute. Be extremely careful not to decorate the controller you are redirecting to with this attribute or you are going to run into an endless loop.
So you could:
[RedirectingAction]
public class HomeController : Controller
{
public ActionResult Index()
{
// This action is never going to execute if the
// redirecting condition is met
return View();
}
}
public class SomeOtherController : Controller
{
public ActionResult SomeAction()
{
return View();
}
}
I have a number of Controllers in my project that all inherit from a controller I've named BaseController. I wrote a custom attribute that I applied to the entire BaseController class, so that each time an action runs in any of my controllers, that attribute will run first.
The problem is that I have a couple of controller actions that I'd like to ignore that attribute, but I don't know how to do it.
Can anyone help? I'm using MVC 1.
Thanks.
In your custom attribute, you can add this ShouldRun() check like this:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (ShouldRun(filterContext))
{
// proceed with your code
}
}
private bool ShouldRun(ActionExecutingContext filterContext)
{
var ignoreAttributes = filterContext.ActionDescriptor.GetCustomAttributes(typeof(IgnoreMyCustomAttribute), false);
if (ignoreAttributes.Length > 0)
return false;
return true;
}
ShouldRun() simply checks whether there's a "IgnoreMyCustomAttribute" on your action. If it's there, then your custom attribute won't do anything.
You'll now want to create a simple IgnoreMyCustomAttribute, which doesn't do anything:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class IgnoreMyCustomAttribute: ActionFilterAttribute
{
}
Whenever you decorate your controller action with [IgnoreMyCustom], then MyCustomAttribute won't do anything. e.g.:
[IgnoreMyCustom]
public ViewResult MyAction() {
}
I had a similar need for something like this and found that by creating an authorization filter (implementing/deriving from FilterAttribute, IAuthorizationFilter) rather than a regular action filter (deriving from ActionFilterAttribute), and setting Inherited=true and AllowMultiple=false on the attribute, that it would only run once at the appropriate spot.
This means I am able to "cascade" my filter down from a base controller (the site-wide default), to a derived controller (for example the AdminController or whatever), or even further down to an individual action method.
For example,
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method, Inherited=true, AllowMultiple=false)]
public class MyCustomAttribute : FilterAttribute, IAuthorizationFilter
{
private MyCustomMode _Mode;
public MyCustomAttribute(MyCustomMode mode)
{
_Mode = mode;
}
public virtual void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
// run my own logic here.
// set the filterContext.Result to anything non-null (such as
// a RedirectResult?) to skip the action method's execution.
//
//
}
}
public enum MyCustomMode
{
Enforce,
Ignore
}
And then to use it, I can apply it to my super-controller,
[MyCustomAttribute(Ignore)]
public class BaseController : Controller
{
}
And I can change/override it for specific controllers, or even for specific actions!
[MyCustomAttribute(Enforce)]
public class AdministrationController : BaseController
{
public ActionResult Index()
{
}
[MyCustomAttribute(Ignore)]
public ActionResult SomeBasicPageSuchAsAHelpDocument()
{
}
}
This allowed me to "turn off" the filter for specific cases, while still being able to apply it as a default on either the whole controller or whole application.
Good luck!
I'm not sure there is an easy way to remove attributes in this situation. But I have done something similar for a project and what I did, as it was only in a few instances I didn't want my attribute to run, was to create two attributes.
My first attribute was applied to my base controller as you've done but it was aware of the existance of a second attribute and by implementing that second attribute I could disable the attribute on the base class from running.
Not sure if it was the best solution but it worked for me.
This was applied to the base controller:
/// <summary>
/// This is used to force the schema to HTTP is it is HTTPS.
/// RequireHttpsAttribute or OptionalHttpsAttribute takes precedence if used.
/// </summary>
public class RequireHttpAttribute : FilterAttribute, IAuthorizationFilter
{
public virtual void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
throw new ArgumentNullException("filterContext");
if (filterContext.HttpContext.Request.IsSecureConnection)
{
object[] attributes = filterContext.ActionDescriptor.GetCustomAttributes(true);
if (!attributes.Any(a => a is RequireHttpsAttribute || a is OptionalHttpsAttribute))
{
HandleHttpsRequest(filterContext);
}
}
}
protected virtual void HandleHttpsRequest(AuthorizationContext filterContext)
{
// only redirect for GET method, otherwise browser may not propogate the verb and request body correctly
if (!string.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException(MvcResources.RequireHttpAttribute_MustNotUseSsl);
// redirect to HTTP version
string url = "http://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
}
Like so:
[RequireHttp]
public abstract class Controller : System.Web.Mvc.Controller
{
}
I could then use what is effectively a dummy attribute to disable it.
/// <summary>
/// This attribute allows the action to be server on HTTP and HTTPS but neither is enforce.
/// RequireHttpsAttribute takes precedence if used.
/// </summary>
public class OptionalHttpsAttribute : FilterAttribute
{
// This is deliberately empty, the attribute is used by RequireHttpAttribute to stop it changing schema to HTTP
}
Like so:
[OptionalHttps]
public ActionResult OptionalHttps()
{
return View();
}
I have a controller decorated with an AuthorizeAttribute. The controller contains several actions that all require authentication apart from one action that requires some custom authentication provided by CustomAuthorizeAttribute.
My question is once I've added [Authorize] at the controller level can I override it (or remove it) with [CustomAuthorize] on just one action? Or do I have to remove [Authorize] from the controller level and add it individually to every other action?
I'm asking purely for convenience because I'm lazy and don't want to decorate every action with the AuthorizeAttribute.
[Authorize]
public class MyController : Controller {
//requires authentication
public ViewResult Admin() {
return View();
}
//... a lot more actions requiring authentication
//requires custom authentication
[CustomAuthorize] //never invoked as already failed at controller level
public ViewResult Home() {
return View();
}
}
In MVC 5 you can override the authorization for any action using the new attribute OverrideAuthorization. Basically, you add it to an action that has a different authorization configuration than the one defined in the controller.
You do it like this:
[OverrideAuthorization]
[Authorize(Roles = "Employee")]
public ActionResult List() { ... }
More information at http://www.c-sharpcorner.com/UploadFile/ff2f08/filter-overrides-in-Asp-Net-mvc-5/
In ASP.NET Core 2.1 there's no OverrideAuthorization attribute and the only thing you can do is make an action anonymous, even if the controller is not. More information at https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-2.1
One option is to do it this way:
[Authorize(Roles = "Admin,Employee")] // admin or employee
public class XController : Controller
{
[Authorize(Roles = "Admin")] // only admin
public ActionResult ActionX() { ... }
[AllowAnonymous] // anyone
public ActionResult ActionX() { ... }
}
You can change the Order in which the attributes run (using the Order property), but I believe that in this case they will still both run unless one generates a result with immediate effect. The key is to have the least restrictive attribute applied at the highest level (class) and get more restrictive for the methods. If you wanted the Home action to be publicly available, for instance, you would need to remove the Authorize attribute from the class, and apply it to each of the other methods.
If the action has the same level of permissiveness, but has a different result, changing the order may be sufficient. For example, you would normally redirect to the Logon action, but for Home you want to redirect to the About action. In this, case give the class attribute Order=2 and the Home action attribute Order=1.
After way too much time, I came up with a solution. You need to decorate your controller with a custom AuthorizeAttribute.
public class OverridableAuthorize : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
var action = filterContext.ActionDescriptor;
if(action.IsDefined(typeof(IgnoreAuthorization), true)) return;
var controller = action.ControllerDescriptor;
if(controller.IsDefined(typeof(IgnoreAuthorization), true)) return;
base.OnAuthorization(filterContext);
}
}
Which can be paired with AllowAnonymous on an Action
[AllowAnonymous]
All you need to override the [Authorize] from the controller, for a specific action is to add
[AllowAnonymous]
to the action you want to not be authorized (then add your custom attribute as required).
See the comments / intellisense :
Represents an attribute that marks controllers and actions to skip the
System.Web.Mvc.AuthorizeAttribute during authorization.
Full Example
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Diagnostics;
using System.Threading.Tasks;
namespace Website
{
public class CustomAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
if (true)//Perform own authorization logic
return; //simply return if request is authorized
context.Result = new UnauthorizedResult();
return; //this is not authorized
}
}
[Authorize]
public class WebsiteController : Controller
{
[HttpGet]
[AllowAnonymous]//When this is added our Custom Attribute is hit, without it our attribute is not used as request already gets 401 from controller's Authorize
[CustomAuthorize]
public IActionResult Index()
{
return View(new ViewModel());
}
}
Note
This approach will not work if you want to use the standard [Authorize] attribute on your action, with a custom policy e.g.
[Authorize]
public class WebsiteController : Controller
{
[HttpGet]
[AllowAnonymous]
[Authorize("CustomPolicyName")] //Will not be run
public IActionResult Index()
{
return View(new ViewModel());
}
}
services.AddAuthorization(options =>
{
options.AddPolicy("BadgeEntry", policy =>
policy.RequireAssertion(context =>
false //Custom logic here
));
});
...but if like the OP you want a Custom Attribute then you are good to go with my solution.
Override for all controllers when handling prototype and production environment.
So there is no need to remove the authorize of each controller.
app.UseEndpoints(endpoint =>
{
endpoint.MapControllers().WithMetadata(new AllowAnonymousAttribute());
});
I am looking to set the result action from a failed IAuthorizationFilter. However I am unsure how to create an ActionResult from inside the Filter. The controller doesn't seem to be accible from inside the filter so my usual View("SomeView") isn't working. Is there a way to get the controler or else another way of creating an actionresult as it doesn't appear to be instantiable?
Doesn't work:
[AttributeUsage(AttributeTargets.Method)]
public sealed class RequiresAuthenticationAttribute : ActionFilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext context)
{
if (!context.HttpContext.User.Identity.IsAuthenticated)
{
context.Result = View("User/Login");
}
}
}
You should look at the implementation of IAuthorizationFilter that comes with the MVC framework, AuthorizeAttribute. If you are using forms authentication, there's no need for you to set the result to User/Login. You can raise a 401 HTTP status response and ASP.NET Will redirect to the login page for you.
The one issue with setting the result to user/login is that the user's address bar is not updated, so they will be on the login page, but the URL won't match. For some people, this is not an issue. But some people want their site's URL to correspond to what the user sees in their browser.
You can instantiate the appropriate ActionResult directly, then set it on the context. For example:
public void OnAuthorization(AuthorizationContext context)
{
if (!context.HttpContext.User.Identity.IsAuthenticated)
{
context.Result = new ViewResult { ViewName = "Whatever" };
}
}