211

About 6 months ago I rolled out a site where every request needed to be over https. The only way at the time I could find to ensure that every request to a page was over https was to check it in the page load event. If the request was not over http I would response.redirect("https://example.com")

Is there a better way -- ideally some setting in the web.config?

Cœur
  • 37,241
  • 25
  • 195
  • 267
codethrift
  • 2,337
  • 4
  • 17
  • 16

15 Answers15

275

Please use HSTS (HTTP Strict Transport Security)

from http://www.hanselman.com/blog/HowToEnableHTTPStrictTransportSecurityHSTSInIIS7.aspx

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <rewrite>
            <rules>
                <rule name="HTTP to HTTPS redirect" stopProcessing="true">
                    <match url="(.*)" />
                    <conditions>
                        <add input="{HTTPS}" pattern="off" ignoreCase="true" />
                    </conditions>
                    <action type="Redirect" url="https://{HTTP_HOST}/{R:1}"
                        redirectType="Permanent" />
                </rule>
            </rules>
            <outboundRules>
                <rule name="Add Strict-Transport-Security when HTTPS" enabled="true">
                    <match serverVariable="RESPONSE_Strict_Transport_Security"
                        pattern=".*" />
                    <conditions>
                        <add input="{HTTPS}" pattern="on" ignoreCase="true" />
                    </conditions>
                    <action type="Rewrite" value="max-age=31536000" />
                </rule>
            </outboundRules>
        </rewrite>
    </system.webServer>
</configuration>

Original Answer (replaced with the above on 4 December 2015)

basically

protected void Application_BeginRequest(Object sender, EventArgs e)
{
   if (HttpContext.Current.Request.IsSecureConnection.Equals(false) && HttpContext.Current.Request.IsLocal.Equals(false))
   {
    Response.Redirect("https://" + Request.ServerVariables["HTTP_HOST"]
+   HttpContext.Current.Request.RawUrl);
   }
}

that would go in the global.asax.cs (or global.asax.vb)

i dont know of a way to specify it in the web.config

MeSo2
  • 450
  • 1
  • 7
  • 18
John Boker
  • 82,559
  • 17
  • 97
  • 130
  • 9
    This works, but it was dangerous for me: when I attempted to run locally in VS 2010 with this code running, my start page never loaded; instead, I just received a "This webpage is not available" message. To fix, I added a second condition to test if the url contains the string "localhost": if it does not, then force https. – mg1075 Aug 25 '11 at 13:43
  • 3
    This is giving me a redirect loop. Before I added the code it worked fine. Any suggestions? – Joe Nov 08 '11 at 04:13
  • +1, It is doing the redirect to server twice? First one, When you are explicitly redirecting and second one, when you are doing redirect in the Global.asax ? – Pankaj Apr 25 '12 at 18:10
  • 13
    Please note that this does not provide any useful security whatsoever. Actually, it will only secure connections from users that are already safe, and will fail to secure those that are being attacked (this is because a MITM can simply omit the redirection altogether and forward everything to your "secure" site). IMHO, redirecting user agents is just feel-good voodoo security, and provides a sometimes dangerous illusion of safety. The only way to go is to instruct the user agents to only request secure resources, not redirect them if they don't. This is what HSTS does -- see answers below. – tne Jun 19 '14 at 12:13
  • You can also add - "GlobalFilters.Filters.Add(new RequireHttpsAttribute());" to "Application_Start()" method in "global.asax.cs" – Manik Arora Mar 25 '15 at 06:41
  • 4
    This answer should be considered 'harmful' and should not be used. As per comment by @tne above. – Rosdi Kasim Mar 26 '15 at 06:49
  • 1
    If you're getting a reported error about your web.config being malformed make sure you have the url rewrite module installed - that had me stuck for 10 minutes or so. http://www.iis.net/downloads/microsoft/url-rewrite – GJKH May 12 '17 at 14:28
  • 2
    @RosdiKasim Should this answer still be considered harmful since the Dec 4 '15 edit? – Andrew Morton May 22 '17 at 08:56
  • 1
    @AndrewMorton The updated answer (using HSTS) is the proper way to do it. It also redirects the browser to https in case the browser is using http. – Rosdi Kasim May 23 '17 at 04:20
  • This doesn't work for me. The site never get redirected to. I edited my web.config as above. The site works fine without the web.config edit and the https site works just fine if I navigate straight there (assuming no web.config changes). Has anyone else had issues with this code? – Jason James Mar 30 '18 at 13:02
  • I get a different problem. No redirect, but I can't get to the https site whether I have the web.config updated or not. firefox says SSL_ERROR_RX_RECORD_TOO_LONG and IE says to turn on TLS 1, 1.1, and 1.2 and try again. – Adam R. Grey Mar 30 '18 at 13:54
  • I've always had to use `` because `off` never worked. Maybe it does now, I don't even try it these days. – ahwm Aug 15 '18 at 22:15
  • I copied these line to my web.config and now https is not working. I deleted these lines from web.config but my site still doesn't work. It seems to me, that these few lines modified something in my system that is connected to this project. – Jánosi Zoltán János Aug 31 '20 at 09:24
  • After hsts is enabled for a domain the browser will not allow a non secure connection. You either need to make https works or clear the hsts for the domain on your browser. – John Boker Sep 01 '20 at 10:52
  • @tne don't you first have to write code to redirect a user from http to https, in order to be able to respond with the HSTS? HSTS is only read by the browser if it's using HTTPS. – David Klempfner Jun 28 '21 at 12:21
  • @DavidKlempfner In an ideal world this is not the best solution. The best would be to encourage end-users to only use HTTPS (it's now built-in to browsers, see "HTTPS-Only Mode" in Firefox for example, but there has always been extensions). HSTS helps if one needs to build lists of sites that can be secured (like HTTPS Everywhere), by declaring "this whole domain supports HTTPS, do use it. An insecure redirection + HSTS can be seen as a form of [TOFU](https://en.wikipedia.org/wiki/Trust_on_first_use) and as such is definitely better than plain HTTP, but will not be better than closing port 80. – tne Jun 30 '21 at 16:12
  • When I paste the revised answer's ` ... ` block into the `` section of my web.config file, there is a 500 error. – Tim Nov 29 '21 at 13:41
130

The other thing you can do is use HSTS by returning the "Strict-Transport-Security" header to the browser. The browser has to support this (and at present, it's primarily Chrome and Firefox that do), but it means that once set, the browser won't make requests to the site over HTTP and will instead translate them to HTTPS requests before issuing them. Try this in combination with a redirect from HTTP:

protected void Application_BeginRequest(Object sender, EventArgs e)
{
  switch (Request.Url.Scheme)
  {
    case "https":
      Response.AddHeader("Strict-Transport-Security", "max-age=300");
      break;
    case "http":
      var path = "https://" + Request.Url.Host + Request.Url.PathAndQuery;
      Response.Status = "301 Moved Permanently";
      Response.AddHeader("Location", path);
      break;
  }
}

Browsers that aren't HSTS aware will just ignore the header but will still get caught by the switch statement and sent over to HTTPS.

Troy Hunt
  • 20,345
  • 13
  • 96
  • 151
  • 6
    Never heard about the HSTS header before, but looks pretty cool. Is there any reason for using such a small max-age value (5 minutes)? The Wikipedia article you link to suggests setting it to a large value (6-12 months). – dana Feb 21 '13 at 19:13
  • 5
    +1. check out this very extensive article on Troy's blog which includes details on why only using redirects can reduce security. Hint: it can leave you vulnerable to the SSL Strip tool, among other things. http://www.troyhunt.com/2011/11/owasp-top-10-for-net-developers-part-9.html – Oran Dennison May 03 '13 at 20:00
  • 3
    Also worth checking out [NWebsec](https://nwebsec.codeplex.com/), which makes this (and more) very easy. – Tieson T. Aug 07 '14 at 03:24
  • 18
    You'll want to wrap the switch in `if(!Request.IsLocal)` so it doesn't break debugging. – Justin J Stark Sep 03 '14 at 16:00
  • 1
    Good answer. One subtlety - For the Http headers ("Strict-Transport-Security") it is better to use library like NWebSec because there are multiple options which are concentrated in one place of configuration rather then spread out here and there. – Ognyan Dimitrov Oct 29 '14 at 14:56
  • @RosdiKasim, the accepted answer was updated for HSTS as well, but completely obviated the "old answer" that showed the redirect-only in code. This is a good one for doing it in code. Can also be written into an `IHttpModule`. – Marc L. Aug 04 '16 at 20:32
  • A quick conversion to VB.net and this solution worked great. – Bryan Lewis May 02 '17 at 16:02
  • Due to a couple of non-tech issues, this is the method I used... except I'm returning "308 Permanent Redirect" which ensures that POSTs stay POSTs for the http redirect. – monty Feb 19 '18 at 00:58
  • I first used montys suggestion to use a 308 instead of 301 because of the POST support, only to find out that a 308 is not supported by IE11. If a significant portion of your visitors is still on IE, this could be a concern. – M-Peror Mar 21 '18 at 13:07
  • Does this handle all traffic to that website? or just the pages you've coded? That is, what about my images and javascript files? – Adam R. Grey Mar 30 '18 at 14:04
92

The IIS7 module will let you redirect.

    <rewrite>
        <rules>
            <rule name="Redirect HTTP to HTTPS" stopProcessing="true">
                <match url="(.*)"/>
                <conditions>
                    <add input="{HTTPS}" pattern="^OFF$"/>
                </conditions>
                <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="SeeOther"/>
            </rule>
        </rules>
    </rewrite>
Mark
  • 9,966
  • 7
  • 37
  • 39
25

For those using ASP.NET MVC. You can use the following to force SSL/TLS over HTTPS over the whole site in two ways:

The Hard Way

1 - Add the RequireHttpsAttribute to the global filters:

GlobalFilters.Filters.Add(new RequireHttpsAttribute());

2 - Force Anti-Forgery tokens to use SSL/TLS:

AntiForgeryConfig.RequireSsl = true;

3 - Require Cookies to require HTTPS by default by changing the Web.config file:

<system.web>
    <httpCookies httpOnlyCookies="true" requireSSL="true" />
</system.web>

4 - Use the NWebSec.Owin NuGet package and add the following line of code to enable Strict Transport Security accross the site. Don't forget to add the Preload directive below and submit your site to the HSTS Preload site. More information here and here. Note that if you are not using OWIN, there is a Web.config method you can read up on on the NWebSec site.

// app is your OWIN IAppBuilder app in Startup.cs
app.UseHsts(options => options.MaxAge(days: 30).Preload());

5 - Use the NWebSec.Owin NuGet package and add the following line of code to enable Public Key Pinning (HPKP) across the site. More information here and here.

// app is your OWIN IAppBuilder app in Startup.cs
app.UseHpkp(options => options
    .Sha256Pins(
        "Base64 encoded SHA-256 hash of your first certificate e.g. cUPcTAZWKaASuYWhhneDttWpY3oBAkE3h2+soZS7sWs=",
        "Base64 encoded SHA-256 hash of your second backup certificate e.g. M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE=")
    .MaxAge(days: 30));

6 - Include the https scheme in any URL's used. Content Security Policy (CSP) HTTP header and Subresource Integrity (SRI) do not play nice when you imit the scheme in some browsers. It is better to be explicit about HTTPS. e.g.

<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.4/bootstrap.min.js"></script>

The Easy Way

Use the ASP.NET MVC Boilerplate Visual Studio project template to generate a project with all of this and much more built in. You can also view the code on GitHub.

Muhammad Rehan Saeed
  • 35,627
  • 39
  • 202
  • 311
  • 3
    Also if using ``, inside you must have `` – Pluto Jun 10 '15 at 17:32
  • 1
    @muhammad-rehan-saeed I am using mvc5 boilerplate but the site does not redirect http to https automatically on production server it does so only on localhost is there something I am missing? – Diin Aug 31 '15 at 11:29
  • This is not the right forum to ask this question. Post an issue on the GitHub site. The ```RequireHttpsAttribute``` does the redirect. As long as you have that it should be fine. – Muhammad Rehan Saeed Aug 31 '15 at 18:55
  • @MuhammadRehanSaeed, love your answer. But... how do I get the SHA256 hash of a certificate created with MakeCert? All I have is a SHA-1 thumbprint... Do you happen to know? – Diana Jul 08 '17 at 06:39
  • 1
    @Diana [this](https://scotthelme.co.uk/hpkp-http-public-key-pinning/) link can show you how. – Muhammad Rehan Saeed Jul 08 '17 at 08:12
13

If you are unable to set this up in IIS for whatever reason, I'd make an HTTP module that does the redirect for you:

using System;
using System.Web;

namespace HttpsOnly
{
    /// <summary>
    /// Redirects the Request to HTTPS if it comes in on an insecure channel.
    /// </summary>
    public class HttpsOnlyModule : IHttpModule
    {
        public void Init(HttpApplication app)
        {
            // Note we cannot trust IsSecureConnection when 
            // in a webfarm, because usually only the load balancer 
            // will come in on a secure port the request will be then 
            // internally redirected to local machine on a specified port.

            // Move this to a config file, if your behind a farm, 
            // set this to the local port used internally.
            int specialPort = 443;

            if (!app.Context.Request.IsSecureConnection 
               || app.Context.Request.Url.Port != specialPort)
            {
               app.Context.Response.Redirect("https://" 
                  + app.Context.Request.ServerVariables["HTTP_HOST"] 
                  + app.Context.Request.RawUrl);    
            }
        }

        public void Dispose()
        {
            // Needed for IHttpModule
        }
    }
}

Then just compile it to a DLL, add it as a reference to your project and place this in web.config:

 <httpModules>
      <add name="HttpsOnlyModule" type="HttpsOnly.HttpsOnlyModule, HttpsOnly" />
 </httpModules>
FlySwat
  • 172,459
  • 74
  • 246
  • 311
  • This seems more involved than just sticking it in the global.asax -- just curious, is there an advantage? – Brian MacKay Oct 16 '09 at 15:22
  • 1
    The advantage would be, when you don't want to use it, just comment out the module in your web.config. This solution *is* configurable, whereas the other is not. – Bob Yexley Nov 19 '09 at 13:19
  • 2
    I am little confused. I'd expected something like `app.BeginRequest += new OnBeginRequest;` in the `Init` method and in the `OnBeginRequest` would contains what current `Init` method contains. Are you sure that this module works as expected? – Jakub Šturc Aug 06 '10 at 10:33
  • It does not work. You do need to add the OnBeginRequest event etc, then it works. – SnAzBaZ Nov 05 '10 at 11:31
  • I would edit this faulty code, but to make it secure you'd also need to use HSTS. Just go with Troy Hunt's answer and make it a module; see https://support.microsoft.com/en-us/kb/307996 (an oldie, but goodie). – Marc L. Aug 04 '16 at 21:28
5

What you need to do is :

1) Add a key inside of web.config, depending upon the production or stage server like below

<add key="HttpsServer" value="stage"/>
             or
<add key="HttpsServer" value="prod"/>

2) Inside your Global.asax file add below method.

void Application_BeginRequest(Object sender, EventArgs e)
{
    //if (ConfigurationManager.AppSettings["HttpsServer"].ToString() == "prod")
    if (ConfigurationManager.AppSettings["HttpsServer"].ToString() == "stage")
    {
        if (!HttpContext.Current.Request.IsSecureConnection)
        {
            if (!Request.Url.GetLeftPart(UriPartial.Authority).Contains("www"))
            {
                HttpContext.Current.Response.Redirect(
                    Request.Url.GetLeftPart(UriPartial.Authority).Replace("http://", "https://www."), true);
            }
            else
            {
                HttpContext.Current.Response.Redirect(
                    Request.Url.GetLeftPart(UriPartial.Authority).Replace("http://", "https://"), true);
            }
        }
    }
}
Chandan Kumar
  • 4,570
  • 4
  • 42
  • 62
5

In IIS10 (Windows 10 and Server 2016), from version 1709 onwards, there is a new, simpler option for enabling HSTS for a website.

Microsoft describe the advantages of the new approach here, and provide many different examples of how to implement the change programmatically or by directly editing the ApplicationHost.config file (which is like web.config but operates at the IIS level, rather than individual site level). ApplicationHost.config can be found in C:\Windows\System32\inetsrv\config.

I've outlined two of the example methods here to avoid link rot.

Method 1 - Edit the ApplicationHost.config file directly Between the <site> tags, add this line:

<hsts enabled="true" max-age="31536000" includeSubDomains="true" redirectHttpToHttps="true" />

Method 2 - Command Line: Execute the following from an elevated command prompt (i.e. right mouse on CMD and run as administrator). Remember to swap Contoso with the name of your site as it appears in IIS Manager.

c:
cd C:\WINDOWS\system32\inetsrv\
appcmd.exe set config -section:system.applicationHost/sites "/[name='Contoso'].hsts.enabled:True" /commit:apphost
appcmd.exe set config -section:system.applicationHost/sites "/[name='Contoso'].hsts.max-age:31536000" /commit:apphost
appcmd.exe set config -section:system.applicationHost/sites "/[name='Contoso'].hsts.includeSubDomains:True" /commit:apphost
appcmd.exe set config -section:system.applicationHost/sites "/[name='Contoso'].hsts.redirectHttpToHttps:True" /commit:apphost

The other methods Microsoft offer in that articles might be better options if you are on a hosted environment where you have limited access.

Keep in mind that IIS10 version 1709 is available on Windows 10 now, but for Windows Server 2016 it is on a different release track, and won't be released as a patch or service pack. See here for details about 1709.

Mike
  • 1,686
  • 19
  • 14
3

If SSL support is not configurable in your site (ie. should be able to turn https on/off) - you can use the [RequireHttps] attribute on any controller / controller action you wish to secure.

yarg
  • 679
  • 6
  • 11
3

This is a fuller answer based on @Troy Hunt's. Add this function to your WebApplication class in Global.asax.cs:

    protected void Application_BeginRequest(Object sender, EventArgs e)
    {
        // Allow https pages in debugging
        if (Request.IsLocal)
        {
            if (Request.Url.Scheme == "http")
            {
                int localSslPort = 44362; // Your local IIS port for HTTPS

                var path = "https://" + Request.Url.Host + ":" + localSslPort + Request.Url.PathAndQuery;

                Response.Status = "301 Moved Permanently";
                Response.AddHeader("Location", path);
            }
        }
        else
        {
            switch (Request.Url.Scheme)
            {
                case "https":
                    Response.AddHeader("Strict-Transport-Security", "max-age=31536000");
                    break;
                case "http":
                    var path = "https://" + Request.Url.Host + Request.Url.PathAndQuery;
                    Response.Status = "301 Moved Permanently";
                    Response.AddHeader("Location", path);
                    break;
            }
        }
    }

(To enable SSL on your local build enable it in the Properties dock for the project)

noelicus
  • 14,468
  • 3
  • 92
  • 111
2

For @Joe above, "This is giving me a redirect loop. Before I added the code it worked fine. Any suggestions? – Joe Nov 8 '11 at 4:13"

This was happening to me as well and what I believe was happening is that there was a load balancer terminating the SSL request in front of the Web server. So, my Web site was always thinking the request was "http", even if the original browser requested it to be "https".

I admit this is a bit hacky, but what worked for me was to implement a "JustRedirected" property that I could leverage to figure out the person was already redirected once. So, I test for specific conditions that warrant the redirect and, if they are met, I set this property (value stored in session) prior to the redirection. Even if the http/https conditions for redirection are met the second time, I bypass the redirection logic and reset the "JustRedirected" session value to false. You'll need your own conditional test logic, but here's a simple implementation of the property:

    public bool JustRedirected
    {
        get
        {
            if (Session[RosadaConst.JUSTREDIRECTED] == null)
                return false;

            return (bool)Session[RosadaConst.JUSTREDIRECTED];
        }
        set
        {
            Session[RosadaConst.JUSTREDIRECTED] = value;
        }
    }
Paul Schroeder
  • 1,460
  • 1
  • 14
  • 21
2

I'm going to throw my two cents in. IF you have access to IIS server side, then you can force HTTPS by use of the protocol bindings. For example, you have a website called Blah. In IIS you'd setup two sites: Blah, and Blah (Redirect). For Blah only configure the HTTPS binding (and FTP if you need to, make sure to force it over a secure connection as well). For Blah (Redirect) only configure the HTTP binding. Lastly, in the HTTP Redirect section for Blah (Redirect) make sure to set a 301 redirect to https://blah.com, with exact destination enabled. Make sure that each site in IIS is pointing to it's own root folder otherwise the Web.config will get all screwed up. Also make sure to have HSTS configured on your HTTPSed site so that subsequent requests by the browser are always forced to HTTPS and no redirects occur.

Gup3rSuR4c
  • 9,145
  • 10
  • 68
  • 126
2

It also depends on the brand of your balancer, for the web mux, you would need to look for http header X-WebMux-SSL-termination: true to figure that incoming traffic was ssl. details here: http://www.cainetworks.com/support/redirect2ssl.html

edorian
  • 38,542
  • 15
  • 125
  • 143
Alexander
  • 29
  • 1
2

I spent sometime looking for best practice that make sense and found the following which worked perfected for me. I hope this will save you sometime.

Using Config file (for example an asp.net website) https://blogs.msdn.microsoft.com/kaushal/2013/05/22/http-to-https-redirects-on-iis-7-x-and-higher/

or on your own server https://www.sslshopper.com/iis7-redirect-http-to-https.html

[SHORT ANSWER] Simply The code below goes inside

<system.webServer> 
 <rewrite>
     <rules>
       <rule name="HTTP/S to HTTPS Redirect" enabled="true" 
           stopProcessing="true">
       <match url="(.*)" />
        <conditions logicalGrouping="MatchAny">
        <add input="{SERVER_PORT_SECURE}" pattern="^0$" />
       </conditions>
       <action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" 
        redirectType="Permanent" />
        </rule>
       </rules>
 </rewrite>
Nour Lababidi
  • 414
  • 4
  • 7
1

-> Simply ADD [RequireHttps] on top of the public class HomeController : Controller.

-> And add GlobalFilters.Filters.Add(new RequireHttpsAttribute()); in 'protected void Application_Start()' method in Global.asax.cs file.

Which forces your entire application to HTTPS.

Santosh K
  • 55
  • 4
  • 1
    I don't believe this will work for any pages served using WebForms or any APIs built with WebAPI. It will only cover MVC controllers. – Marc L. Aug 04 '16 at 18:48
1

If you are using ASP.NET Core you could try out the nuget package SaidOut.AspNetCore.HttpsWithStrictTransportSecurity.

Then you only need to add

app.UseHttpsWithHsts(HttpsMode.AllowedRedirectForGet, configureRoutes: routeAction);

This will also add HTTP StrictTransportSecurity header to all request made using https scheme.

Example code and documentation https://github.com/saidout/saidout-aspnetcore-httpswithstricttransportsecurity#example-code