Upgrading browsers visiting your website to a secure connection is a best practice and is easy to do. I have decided to share my implementation because I have seen many partial or insecure implementations. Correct implementation satisfies both backward compatibility and security requirements of various web browsers. This article covers what you need to know before you start redirecting your users to the HTTPS protocol.
What attackers do
Some naïve web developers use an HTTPS connection only when the browser sends a form with sensitive data to the server. When wireless network encryption is weak (which is the case for most public wireless networks or cellular data networks), the attacker can modify the server’s response so that the HTML form will be sent to the attacker’s website. That’s why it is essential to bypass unencrypted connections completely.
Upgrade-Insecure-Requests HTTP header
The browser can send an Upgrade-Insecure-Requests: 1
header in a request. By applying this header, it instructs the server that the client prefers an encrypted connection. Who wants an unencrypted connection? For example, web crawlers can save processor time because an optical connection between datacenters is harder to intercept (it is at least much more noticeable). Web crawlers are mostly interested in public data only.
It would be a bad practice to automatically redirect every request to HTTPS. Web APIs can accept large requests through POST requests. When a POST request is redirected (enabled by RFC7231, which obsoletes RFC2316), the client must transfer the whole payload repeatedly. It may be better to return an HTTP client error status code to force the client to send the request straight to the HTTPS endpoint.
Strict-Transport-Security HTTP header
The server can send a Strict-Transport-Security: max-age=<expire-time>
header in a response. By applying this header, it instructs the browser to automatically convert all attempts to open the site to an HTTPS connection. Browsers accept this header only if a secure connection is already established because an attacker may remove this header when your site is using plain HTTP.
A website redirection from HTTP to HTTPS isn’t secure because an attacker may replace the location to which the browser is redirected. The Strict-Transport-Security (HSTS) header was intended to bypass this opportunity for a man-in-the-middle attack. There is a good chance that the user visits your site from a non-compromised network. When they visit your site afterwards, the browser will always use HTTPS, and the attacker will miss an opportunity for a man-in-the-middle attack even in a compromised network.
ASP.NET MVC action filter for HTTPS
An action filter is an attribute that you can apply to an action or an entire controller. It modifies the way in which the request is executed. The TlsAttribute
redirects users to a secure connection when the browser sends the Upgrade-Insecure-Requests
header and the request isn’t from localhost
. If the connection is secure, it adds an HSTS header to the server’s response.
public class TlsAttribute : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext) {
var request = filterContext.HttpContext.Request;
if (request.IsSecureConnection) {
filterContext.HttpContext.Response.AddHeader("Strict-Transport-Security", "max-age=15552000");
} else if (!request.IsLocal && request.Headers["Upgrade-Insecure-Requests"] == "1") {
var url = new Uri("https://" + request.Url.GetComponents(UriComponents.Host | UriComponents.PathAndQuery, UriFormat.Unescaped), UriKind.Absolute);
filterContext.Result = new RedirectResult(url.AbsoluteUri);
}
}
}
Then the action filter can be easily applied.
[HttpGet, Tls]
public ActionResult BlogPost(int id) {
...
}