Marius Schulz
Marius Schulz
Front End Engineer

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:

Chrome Developer Tools showing a site's cookies

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 the Secure flag.
  • CookieSecurePolicy.Always always sets the Secure flag.
  • CookieSecurePolicy.SameAsRequest only sets the Secure 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.