How to redirect to HTTPS in ASP.NET MVC application correctly

, 4 minutes to read

Up­grad­ing browsers vis­it­ing your web­site to se­cure con­nec­tion is a best prac­tice and it is easy to do. I have de­cided to share my im­ple­men­ta­tion be­cause I had seen many par­tial or in­se­cure im­ple­men­ta­tions. Cor­rect im­ple­men­ta­tion sat­is­fies both back­ward com­pat­i­bil­ity and se­cu­rity re­quire­ments of var­i­ous web browsers. This ar­ti­cle covers what you need to know be­fore you start to redi­rect your users to HTTPS pro­to­col.

What attackers do

Some naïve web de­vel­op­ers use HTTPS con­nec­tion only when the browser sends a form with sen­si­tive data to the server. Once a wire­less net­work en­cryp­tion is weak (which is the case of the most public wire­less net­works or cel­lu­lar data net­works), the at­tacker can mod­ify server’s re­sponse in a way that the HTML form will be sent to at­tacker’s web­site. That’s why it is essen­tial to by­pass un­en­crypted con­nec­tion com­pletely.

Upgrade-Insecure-Requests HTTP header

The browser can send an Up­grade-In­se­cure-Re­quests: 1 header in a re­quest. By ap­ply­ing this header, it in­structs the server that the client pre­fers an en­crypted con­nec­tion. Who wants an un­en­crypted con­nec­tion? For ex­am­ple, web crawlers can save pro­ces­sor’s time be­cause an op­ti­cal con­nec­tion be­tween dat­a­cen­ters is harder to in­ter­cept (it is at least much more no­tice­able). Web crawlers are mostly in­ter­ested in public data only.

It would be a bad prac­tice to au­to­mat­i­cally redi­rect ev­ery re­quest to HTTPS. Web API can ac­cept large re­quests through POST re­quests. When the POST request is redi­rected (it now en­ables RFC7231 which ob­so­letes RFC2316), the client must trans­fer whole pay­load re­peat­edly. It may be bet­ter to re­turn a HTTP client er­ror sta­tus code to force the client sending the re­quest straight to the HTTPS end­point.

Strict-Transport-Security HTTP header

The server can send a Strict-Trans­port-Se­cu­rity: max-age=<ex­pire-time> in a re­sponse. By ap­ply­ing this header, it in­structs the browser to au­to­mat­i­cally con­vert all at­tempts to open the site to HTTPS con­nec­tion. Browsers ac­cept this header only if se­cure con­nec­tion is al­ready estab­lished be­cause an at­tacker may re­move this header when your site is us­ing plain HTTP.

A web­site redi­rec­tion from HTTP to HTTPS isn’t se­cure be­cause an at­tacker may re­place the lo­ca­tion which the browser is redi­rected to. Strict-Trans­port-Se­cu­rity (HSTS) header was in­tended to by­pass this op­por­tu­nity for a man-in-the-mid­dle at­tack. There is a good chance that the user vis­its your site from a non-com­promised net­work. When he vis­its your site af­ter­wards, the browser will al­ways use HTTPS and the at­tacker will miss an op­por­tu­nity to man-in-the-mid­dle at­tack even in a com­promised net­work.

ASP.NET MVC action filter for HTTPS

An ac­tion fil­ter is an at­tribute that you can ap­ply to an ac­tion or an en­tire con­troller. It mod­i­fies the way in which the re­quest is ex­e­cuted. The Tl­sAt­tribute redi­rects users to se­cure con­nec­tion when the browser sends the Up­grade-In­se­cure-Re­quests header and the re­quest isn’t lo­cal­host. If the con­nec­tion is se­cure, it adds a HSTS header to the server’s re­sponse.

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 ac­tion fil­ter can be eas­ily ap­plied.

[HttpGet, Tls] public ActionResult BlogPost(int id) { ... }