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();
}
Alex
  • 34,581
  • 26
  • 91
  • 135
  • 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. – Paul Hadfield Sep 02 '10 at 14:48
  • You are right. The problem has been solved and I updated my main post with the class that helped me. – Alex Sep 02 '10 at 15:13

5 Answers5

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).

Damian Green
  • 6,895
  • 2
  • 31
  • 43
Chao
  • 3,033
  • 3
  • 30
  • 36
  • This method could be modified to also not require HTTPS in test environments. – Jeff Siver Sep 02 '10 at 14:52
  • I used your code and MVC's source code to create a solution. Thanks! – Alex Sep 02 '10 at 15:06
  • ok when reading [this](http://tech.trailmax.info/2014/02/implemnting-https-everywhere-in-asp-net-mvc-application/) and [this](https://blogs.msdn.microsoft.com/rickandy/2012/03/23/securing-your-asp-net-mvc-4-app-and-the-new-allowanonymous-attribute/) as a security measure should we add `filters.Add(new RequireSSLAttribute ());` in `FilterConfig` ? – Shaiju T Feb 22 '16 at 15:21
  • 1
    @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. – Chao Jul 25 '16 at 14:57
  • 2
    You could use filterContext.HttpContext.Request.IsLocal instead of searching for the string – Jonathan Sayce Apr 24 '17 at 12:44
20
    public static void RegisterGlobalFilters(GlobalFilterCollection filters) {

        if (!HttpContext.Current.IsDebuggingEnabled) {
            filters.Add(new RequireHttpsAttribute());
        }
    }
anon
  • 281
  • 3
  • 4
  • 4
    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. – Andrew Dec 05 '14 at 21:17
  • 4
    In my current MVC project, there was a default file in the App_Start folder called FilterConfig.cs. Add it there. – fabspro Feb 21 '15 at 05:45
14
#if (!DEBUG)
[RequireHttps]
#endif
public ActionResult Login()
{
    if (Helper.LoggedIn)
    {
        Response.Redirect("/account/stats");
    }

    return View();
}
Femaref
  • 60,705
  • 7
  • 138
  • 176
  • 2
    I thought about this too. But it means that I have to add #if DEBUG for every RequireHttps attribute. – Alex Sep 02 '10 at 14:56
  • ah, I thought it was only one occasion. The other proposals are better then. – Femaref Sep 02 '10 at 15:44
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
    }
}
joshperry
  • 41,167
  • 16
  • 88
  • 103
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
}
Nick Niebling
  • 317
  • 3
  • 12