HTTP Strict Transport Security (HSTS) is an opt-in security enhancement that is specified by a web application through the use of a special response header. Once a supported browser receives this header that browser will prevent any communications from being sent over HTTP to the specified domain and will instead send all communications over HTTPS.
Currently, there is no built in support for HSTS in MVC. So I decided to create an filter attribute that I can use as part of my application. There are a few questions about the code I have written which are:
- I have used an action filter for granularity, but would a module/handler been better? If so, why?
- Should the default of the attribute be to include sub domains, or exclude them (currently it excludes them)
- The default timeout for the header is 300 seconds. This is a randomly picked arbitrary value. Is this value too much/too little?
The attribute is designed to compliment the RequireHttpsAttribute
using System;
using System.Globalization;
using System.Web.Mvc;
public class HSTSAttribute : ActionFilterAttribute
{
private const string HeaderName = "Strict-Transport-Security";
private readonly string _headerValue;
private readonly bool _includeSubDomains;
public HSTSAttribute(bool includeSubDomains = false)
: this(300, includeSubDomains)
{
}
public HSTSAttribute(TimeSpan duration, bool includeSubDomains = false)
: this(duration == null ? 300 : duration.Seconds, includeSubDomains)
{
}
public HSTSAttribute(int duration, bool includeSubDomains = false)
{
if (duration <= 0)
{
throw new ArgumentException();
}
var includeSubDomainsSuffix = includeSubDomains ? String.Empty : "; includeSubDomains";
_headerValue = String.Format("max-age={0}{1}", duration.ToString(CultureInfo.InvariantCulture)
, includeSubDomainsSuffix);
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
// HSTS headers should be sent via HTTPS responses only : http://tools.ietf.org/html/draft-ietf-websec-strict-transport-sec-14#section-7.2
// They should also not be duplicated
if (filterContext.HttpContext.Request.IsSecureConnection
&& filterContext.HttpContext.Response.Headers[HeaderName] == null)
{
filterContext.HttpContext.Response.AddHeader(HeaderName, _headerValue);
}
base.OnResultExecuted(filterContext);
}
}