Implementing always signed in with ASP.NET Core and Azure

While migrating the elmah.io app to ASP.NET Core, I had to re-implement the always signed in feature available to elmah.io users. It's a toggle that the user can set and as a result, he/she won't be automatically logged out of the application. In this post, I'll share how I implemented this and the findings I discovered on the way. For a general introduction to how authentication is implemented on the elmah.io app, check out Cookie authentication with social providers in ASP.NET Core.

We use cookie-based authentication on the elmah.io app. As shown in the linked blog post, signing in users happens by calling the SignInAsync method on the HttpContext:

await HttpContext.SignInAsync(principal);

To implement always signed in you will need to tell ASP.NET Core to generate a non-expiring cookie. To do this, use the overload of the SignInAsync method, accepting custom authentication properties.

await HttpContext.SignInAsync(principal, authProperties);

The authProperties object is of type AuthenticationProperties and contains a range of properties for controlling authentication. To make the generated cookie persistent and never expiring, use properties like shown here:

var authProperties = new AuthenticationProperties
{
    ExpiresUtc = DateTime.UtcNow.AddYears(100),
    IsPersistent = true,
};

That's it. The cookie won't expire for 100 years, which is probably long enough for most websites.

If you are hosting on a single server on Azure or an IIS server hosted elsewhere you are good to go. In our case, we host on multiple servers and have deployment slots for different environments like Production and Staging. The challenge when running on a set up like this is that the encryption keys used to generate the authentication token is specific to the machine the website is hosted on. This means that swapping deployment slots, will invalidate all of the cookie tokens, even though we marked them as persistent and never expiring.

Monitor Azure websites, functions, and more with elmah.io

➡️ elmah.io for Azure ⬅️

To fix this you need to enable data protection and share encryption keys between servers. When running on Azure, this is most easily achieved using built-in support for Azure Blob Storage.

Start by creating a new storage account on Azure. For this example, I have selected a locally-redundant general purpose v1 storage account. Whether you want local or geo-redundant storage depends on your set up and company policies. As for the storage version, v1 is cheaper than v2 and you won't need any of the features available on v2 for this account.

In your newly created storage account, create a new container and name it something valid:

Time to update the ASP.NET Core application. Include the following code in the ConfigureServices method:

var storageUri = new Uri("SAS_TOKEN");
var blobClient = new CloudBlobClient(storageUri);
var container = blobClient.GetContainerReference("data-protection-container-name");
container.CreateIfNotExists();
services
    .AddDataProtection()
    .SetApplicationName("YOUR_APP_NAME")
    .PersistKeysToAzureBlobStorage(container, "data-protection-blob-name");
You will need to replace a couple of values in the code. SAS_TOKEN should be replaced with a valid SAS token URL. Check out Grant limited access to Azure Storage resources using shared access signatures (SAS) for details on how to generate this. YOUR_APP_NAME should be replaced with an application name of your choice.

Your application now supports always signed in and won't log out users when hitting different servers and/or deployment slots on Azure.