HTTPS is pretty awesome. It not only encrypts the traffic between the client and server so others can't see it, but also prevents others from modifying the content. So it also provides integrity. And being that you can now have HTTPS for free with services like Let's Encrypt, most apps should start to look into using HTTPS.
So I was thinking that I would need a small, re-usable middleware component for ASP.NET Core that:
- Checks if the request is done over HTTPS
- If not, makes a permanent redirect (301) to the secure version of the URL
- If yes, just allows the request to continue
Seems pretty simple right? Let's start with an empty middleware template:
public class EnforceHttpsMiddleware
{
private readonly RequestDelegate _next;
public EnforceHttpsMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
}
}
Now for the logic. We can easily check if the request is secure from the HttpContext
:
HttpRequest req = context.Request;
if (req.IsHttps == false)
{
}
Then we just need to get the full URL without the scheme, and concatenate it with "https://":
string url = "https://" + req.Host + req.Path + req.QueryString;
Host
contains the host name, e.g. "mysite.com". Path
contains the request path, e.g. "/products/1/edit". And the QueryString
is of course the query string, e.g. "?param1=something¶m2=something". So concatenating all of them we get the full URL, but with the scheme changed to https://.
Then we need to do a permanent redirect:
context.Response.Redirect(url, permanent: true);
The completed middleware
Putting all of it together, we get a middleware class that looks like this:
public class EnforceHttpsMiddleware
{
private readonly RequestDelegate _next;
public EnforceHttpsMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
HttpRequest req = context.Request;
if (req.IsHttps == false)
{
string url = "https://" + req.Host + req.Path + req.QueryString;
context.Response.Redirect(url, permanent: true);
}
else
{
await _next(context);
}
}
}
Now of course we should also make an extension method for IApplicationBuilder so we can register the middleware with a simple function call:
public static class AppBuilderExtensions
{
public static IApplicationBuilder UseHttpsEnforcement(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
return app.UseMiddleware<EnforceHttpsMiddleware>();
}
}
We can then use it in Startup.cs's Configure function like so:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseHttpsEnforcement();
}
The important thing is that it should be the first middleware that requests hit, so nothing can be served insecurely. I would also choose to only use it outside of Development environment, since in there the HTTPS port is usually non-standard, making the request fail.
In a future post I will look into how we can enable Strict Transport Security, which will add to the security of the app. Since even if you force the redirects, a user can still first hit your site with an insecure request, which can be intercepted. Without Strict Transport Security our app is still vulnerable to Man-In-The-Middle attacks.