Enable HTTP Strict Transport Security (HSTS) in IIS 7

Solution 1:

This allows us to handle both the HTTP redirect and add the Strict-Transport-Security header to HTTPS responses with a single IIS site (URL Rewrite module has to be installed):

<?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}{REQUEST_URI}"
                        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; includeSubDomains; preload" />
                </rule>
            </outboundRules>
        </rewrite>
    </system.webServer>
</configuration>

Solution 2:

To supplement voretaq7's answer, you could also do this using the Web.config file (N.B.: To be used for SSL sites only, since it will add the header for both HTTP and HTTPS responses, which is against the RFC 6797 specification, please see the explanation below) — add a block as follows:

<system.webServer>
    <httpProtocol>
        <customHeaders>
            <add name="Strict-Transport-Security" value="max-age=31536000"/>
        </customHeaders>
    </httpProtocol>
</system.webServer>

Obviously, you may already have a system.webServer block in your Web.config, so add this to that, if so. We prefer handling things in the Web.config rather than the GUI, because it means the config changes can be committed to our Git repository.

If you wanted to handle the HTTP-to-SSL redirection, as Greg Askew mentioned, you might find it easier to do that with a separate website in IIS. This is how we handle requiring SSL for some client sites. That site contains only an HTTP redirect and some information-disclosure fixes, all in the Web.config:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.web>
    <httpRuntime requestValidationMode="2.0" enableVersionHeader="false" />
  </system.web>
  <system.webServer>
    <httpRedirect enabled="true" destination="https://www.domain.co.uk/"
      httpResponseStatus="Permanent" />
    <httpProtocol>
      <customHeaders>
        <remove name="X-Powered-By" />
      </customHeaders>
    </httpProtocol>
    <rewrite>
      <outboundRules>
        <rule name="Remove RESPONSE_Server">
          <match serverVariable="RESPONSE_Server" pattern=".+" />
          <action type="Rewrite" value="" />
        </rule>
      </outboundRules>
    </rewrite>
  </system.webServer>
</configuration>

This is our preferred solution for a couple of reasons — we can easily log redirected traffic separately (as it's in a different IIS log), it doesn't involve more code in the Global.asax.cs (we don't have any code in there, which is a little more convenient for an Umbraco site) and, importantly, it means that all the config is still held in our GIT repo.

Edited to add: To be clear, in order to comply with RFC 6797, the Strict-Transport-Security custom header MUST NOT be added to requests made by unencrypted HTTP. To be RFC6797-compliant, you MUST have two sites in IIS, as I've described after the first code block. As Chris points out, RFC 6797 includes:

An HSTS Host MUST NOT include the STS header field in HTTP responses conveyed over non-secure transport.

so sending the Strict-Transport-Security customer header in response to a non-SSL request would not comply with the specification.


Solution 3:

IIS has the ability to add custom headers to responses. This would seem to be the easiest way to go about it.

According to the documentation on IIS.net you can add these headers through IIS Manager:

  • In the Connections pane, go to the site, application, or directory for which you want to set a custom HTTP header.
  • In the Home pane, double-click HTTP Response Headers.
  • In the HTTP Response Headers pane, click Add... in the Actions pane.
  • In the Add Custom HTTP Response Header dialog box, set the name and value for your custom header, and then click OK.

Solution 4:

I would use the example from the Wikipedia link you referenced and perform the activity in global.asax for the site. This enables redirecting the request to an https url, and then insert the header into the response.

This is due to the HSTS header must be ignored if it isn't in an https response.

protected void Application_BeginRequest()
{
    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;
    }
}

Solution 5:

This seems to be a pretty fail safe way of doing this. Add this code in the Global.asax - the Application_BeginRequest event fires first in the Asp.net request lifecycle: http://msdn.microsoft.com/en-us/library/system.web.httpapplication.beginrequest(v=vs.110).aspx

Per the spec, http requests must not respond with the header - so this code only adds it for https requests. Max-age is in number of seconds, and it's usually a good idea to put a large value in here (IE - 31536000 indicates the site will run SSL only for the next 365 days)

protected void Application_BeginRequest(Object sender, EventArgs e)
{
  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;
  }
}

Tags:

Iis 7