Strict Transport Security in ASP.NET MVC. This ASP.NET MVC attribute that forces an unsecured HTTP request to be re-sent over HTTPS and adds HSTS headers to secured requests.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
// article: http://tpeczek.blogspot.com/2015/07/strict-transport-security-in-aspnet-mvc.html
// source: https://github.com/tpeczek/Lib.Web.Mvc/blob/master/Lib.Web.Mvc/RequireHstsAttribute.cs
namespace Mvc
{
/// <summary>
/// Represents an attribute that forces an unsecured HTTP request to be
/// re-sent over HTTPS and adds HSTS headers to secured requests.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class RequireHstsAttribute : RequireHttpsAttribute
{
private readonly uint _maxAge;
private const string _strictTransportSecurityHeader = "Strict-Transport-Security";
private const string _maxAgeDirectiveFormat = "max-age={0}";
private const string _includeSubDomainsDirective = "; includeSubDomains";
private const string _preloadDirective = "; preload";
private const int _minimumPreloadMaxAge = 10886400;
/// <summary>
/// Gets the time (in seconds) that the browser should remember that
/// this resource is only to be accessed using HTTPS.
/// </summary>
public uint MaxAge { get { return _maxAge; } }
/// <summary>
/// Gets or sets the value indicating if this rule applies to all
/// subdomains as well.
/// </summary>
public bool IncludeSubDomains { get; set; }
/// <summary>
/// Gets or sets the value indicating if subscription to HSTS preload
/// list (https://hstspreload.appspot.com/) should be confirmed.
/// </summary>
public bool Preload { get; set; }
/// <summary>
/// Initializes a new instance of the RequireHstsAttribute class.
/// </summary>
/// <param name="maxAge">The time (in seconds) that the browser
/// should remember
/// that this resource is only to be accessed using HTTPS.</param>
public RequireHstsAttribute(uint maxAge) : base()
{
_maxAge = maxAge;
IncludeSubDomains = false;
Preload = false;
}
/// <summary>
/// Determines whether a request is secured (HTTPS). If it is sets the
/// Strict-Transport-Security header. If it is not calls the
/// HandleNonHttpsRequest method.
/// </summary>
/// <param name="filterContext"></param>
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (filterContext.HttpContext.Request.IsSecureConnection)
{
if (Preload && (MaxAge < _minimumPreloadMaxAge))
{
throw new InvalidOperationException("In order to confirm HSTS preload list subscription expiry must be at least eighteen weeks (10886400 seconds).");
}
if (Preload && !IncludeSubDomains)
{
throw new InvalidOperationException("In order to confirm HSTS preload list subscription subdomains must be included.");
}
StringBuilder headerBuilder = new StringBuilder();
headerBuilder.AppendFormat(_maxAgeDirectiveFormat, _maxAge);
if (IncludeSubDomains)
{
headerBuilder.Append(_includeSubDomainsDirective);
}
if (Preload)
{
headerBuilder.Append(_preloadDirective);
}
filterContext.HttpContext.Response.AppendHeader(_strictTransportSecurityHeader, headerBuilder.ToString());
}
else
{
HandleNonHttpsRequest(filterContext);
}
}
}
}