ASP.NET MVC: How to automatically disable [RequireHttps] on localhost?
Asked Answered
F

5

29

I want my login page to be SSL only:

    [RequireHttps]
    public ActionResult Login()
    {
        if (Helper.LoggedIn)
        {
            Response.Redirect("/account/stats");
        }

        return View();
    }

But obviously it doesn't work on localhost when I develop and debug my application. I don't wanna use IIS 7 with SSL certificates, how can I automatically disable the RequireHttps attribute?

Update

Based on info provided by StackOverflow users and ASP.NET MVC 2 source code I created the following class that solves the problem.

public class RequireSSLAttribute : FilterAttribute, IAuthorizationFilter
{
    public virtual void OnAuthorization(AuthorizationContext filterContext)
    {
        if (filterContext == null)
        {
            throw new ArgumentNullException("filterContext");
        }

        if (!filterContext.HttpContext.Request.IsSecureConnection)
        {
            HandleNonHttpsRequest(filterContext);
        }
    }

    protected virtual void HandleNonHttpsRequest(AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.Url.Host.Contains("localhost")) return;

        if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("The requested resource can only be accessed via SSL");
        }

        string url = "https://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
        filterContext.Result = new RedirectResult(url);
    }
}

And it's used like this:

[RequireSSL]
public ActionResult Login()
{
    if (Helper.LoggedIn)
    {
        Response.Redirect("/account/stats");
    }

    return View();
}
Fluorspar answered 2/9, 2010 at 14:37 Comment(2)
If you search Stack Overflow for "RequireHttps" the question has been asked a couple of times, so lots of information regarding solution. It's a good question however, I've not used that attribute yet but I can see it being an issue straight away.Elidaelidad
You are right. The problem has been solved and I updated my main post with the class that helped me.Fluorspar
I
26

The easiest thing would be to derive a new attribute from RequireHttps and override HandleNonHttpsRequest

protected override void HandleNonHttpsRequest(AuthorizationContext filterContext)
        {
            if (!filterContext.HttpContext.Request.Url.Host.Contains("localhost"))
            {
                base.HandleNonHttpsRequest(filterContext);
            }
        }

HandleNonHttpsRequest is the method that throws the exception, here all we're doing is not calling it if the host is localhost (and as Jeff says in his comment you could extend this to test environments or in fact any other exceptions you want).

Instil answered 2/9, 2010 at 14:49 Comment(5)
This method could be modified to also not require HTTPS in test environments.Stebbins
I used your code and MVC's source code to create a solution. Thanks!Fluorspar
ok when reading this and this as a security measure should we add filters.Add(new RequireSSLAttribute ()); in FilterConfig ?Oink
@stom the question was specifically about how to disable the attribute for localhost but if we are talking about securing your application as a whole yes you should be adding it to your globalfilters, you should probably set the HTTP Strict Transport Security header too.Instil
You could use filterContext.HttpContext.Request.IsLocal instead of searching for the stringEdmund
T
20
    public static void RegisterGlobalFilters(GlobalFilterCollection filters) {

        if (!HttpContext.Current.IsDebuggingEnabled) {
            filters.Add(new RequireHttpsAttribute());
        }
    }
Toddler answered 5/12, 2014 at 20:49 Comment(2)
It would be more helpful to add a little information regarding where to put this code, just to help those more unfamiliar with ASP.NET MVC.Glossator
In my current MVC project, there was a default file in the App_Start folder called FilterConfig.cs. Add it there.Appetizing
G
14
#if (!DEBUG)
[RequireHttps]
#endif
public ActionResult Login()
{
    if (Helper.LoggedIn)
    {
        Response.Redirect("/account/stats");
    }

    return View();
}
Gaal answered 2/9, 2010 at 14:46 Comment(2)
I thought about this too. But it means that I have to add #if DEBUG for every RequireHttps attribute.Fluorspar
ah, I thought it was only one occasion. The other proposals are better then.Gaal
T
8

You can encapsulate this requirement in a derived attribute:

class RequireHttpsNonDebugAttribute : RequireHttpsAttribute {
    public override void HandleNonHttpsRequest(AuthorizationContext ctx) {
        #if (!DEBUG)
        base.HandleNonHttpsRequest(ctx);
        #endif
    }
}
Tunicate answered 2/9, 2010 at 14:51 Comment(0)
K
1

MVC 6 (ASP.NET Core 1.0):

The proper solution would be to use env.IsProduction() or env.IsDevelopment().

Example:

Startup.cs - AddMvc with a custom filter:

public void ConfigureServices(IServiceCollection services)
{
    // TODO: Register other services

    services.AddMvc(options =>
    {
        options.Filters.Add(typeof(RequireHttpsInProductionAttribute));
    });
}

Custom filter inherit from RequireHttpsAttribute

public class RequireHttpsInProductionAttribute : RequireHttpsAttribute
{
    private bool IsProduction { get; }

    public RequireHttpsInProductionAttribute(IHostingEnvironment environment)
    {
        if (environment == null)
            throw new ArgumentNullException(nameof(environment));
        this.IsProduction = environment.IsProduction(); 
    }
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        if (this.IsProduction)
            base.OnAuthorization(filterContext);
    }
    protected override void HandleNonHttpsRequest(AuthorizationContext filterContext)
    {
        if(this.IsProduction)
            base.HandleNonHttpsRequest(filterContext);
    }
}

Design decisions explained:

  1. Use environment IsProduction() or IsDevelopment() over e.g. "#if DEBUG". Sometimes we release/publish in DEBUG mode on our test server and don't want to disable this security requirement. This needs to be disabled only in localhost/development (since we are too lazy to setup localhost SSL in IIS Express or whatever we use locally).
  2. Use filter in Startup.cs for global setup (since we want this to apply everywhere). Startup should be responsible for registering and setting up all global rules. If your company employ a new developer, she would expect to find global setup in Startup.cs.
  3. Use RequireHttpsAttribute logic since it's proven (by Microsoft). The only thing we want to change is when logic is applied (production) and when it's not (development/localhost). Never use "magical" strings like "http://" and "https://" when it can be avoided by reusing a Microsoft component created to provide the same logic.

Above I would consider the "proper" solution.

Note:

As an alternative, we could make a "class BaseController : Controller" and make all our controllers inherit from "BaseController" (instead of Controller). Then we only have to set the attribute 1 global place (and don't need to register filter in Startup.cs).

Some people prefer the attribute style. Please note this will eliminate design decision #2's benefits.

Example of usage:

[RequireHttpsInProductionAttribute]
public class BaseController : Controller
{
    // Maybe you have other shared controller logic..
}

public class HomeController : BaseController
{
    // Add endpoints (GET / POST) for Home controller
}
Koine answered 7/7, 2016 at 12:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.