Securing Authentication Cookies in ASP.NET Core
Most web frameworks provide functionality for working with authentication cookies, and so does ASP.NET Core. The Microsoft.AspNetCore.Authentication.Cookies NuGet package implements cookie middleware that serializes a user principal into an encrypted cookie. The same middleware later validates the cookie, deserializes the user, and fills the HttpContext.User
property.
The cookie authentication middleware is registered within the Configure
method of the Startup
class. It should come earlier in the HTTP request pipeline than MVC (or whatever framework you're using). Otherwise, the framework can't know about the authentication status of the current user:
public void Configure(IApplicationBuilder app)
{
// ...
app.UseCookieAuthentication();
app.UseMvc();
}
The UseCookieAuthentication
extension method follows the UseXXX
pattern that is so typical for building the ASP.NET Core HTTP request pipeline. It accepts an options parameter through which the cookie authentication middleware can be configured:
public void Configure(IApplicationBuilder app)
{
// ...
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies",
AutomaticAuthenticate = true,
AutomaticChallenge = true,
LoginPath = "/login",
// More properties
});
app.UseMvc();
}
For a full list of options, head over to the ASP.NET Core documentation. Here, I'd like to highlight two options that are important for the protection of the authentication cookie: CookieHttpOnly
and CookieSecure
. As their names suggest, they configure the cookie's HttpOnly
and Secure
flags. Those can be inspected in your browser's developer tools:
The HttpOnly
flag tells the browser to make the cookie inaccessible to client-side scripts. That way, the cookie is still sent as an HTTP header, but malicious JavaScript code can't access it via the document.cookie
property. The CookieHttpOnly
option is true
by default, which is why I haven't explicitly set it in the above configuration example.
The Secure
flag instructs the browser to only include the cookie header in requests sent over HTTPS. That way, the cookie is never sent over an unsecured HTTP connection. There's an enumeration called CookieSecurePolicy
in ASP.NET Core with the following three cases:
CookieSecurePolicy.None
never sets theSecure
flag.CookieSecurePolicy.Always
always sets theSecure
flag.CookieSecurePolicy.SameAsRequest
only sets theSecure
flag if the cookie was set in the response to an HTTPS request.
Always setting the Secure
flag is the most restrictive and most secure option. This is the one you should be targeting if your production environment fully runs on HTTPS (and it should). However, this approach doesn't "just work" on your development machine(s) because you'd need to set up SSL certificates locally in order to use HTTPS.
The solution to this problem is to set a different CookieSecurePolicy
option, depending on the environment the web application is running in. During development — that is, in the Development
environment — the SameAsRequest
option is a good choice, while in all other environments (Production
, Staging
, …) the Always
option should be set.
You can find out about the current environment by injecting the IHostingEnvironment
service into the Configure
method and querying its IsDevelopment()
, IsStaging()
, or IsProduction()
methods:
public void Configure(IApplicationBuilder app,
IHostingEnvironment hostingEnvironment)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies",
AutomaticAuthenticate = true,
AutomaticChallenge = true,
LoginPath = "/login",
CookieSecure = hostingEnvironment.IsDevelopment()
? CookieSecurePolicy.SameAsRequest
: CookieSecurePolicy.Always
});
}
And there you go! By always setting the Secure
flag in production, you're protected from accidentally leaking the authentication cookie if, for some reason, the cookie was set in the response to an unsecured HTTP request.