Cookie encryption

  Annice      2021-03-19      C#, ASP.NET Core, Security

Cookies are commonly used in web development contexts, e.g. to remember a specific user login by having a cookie created based on a logged in user ID. However, cookie values saved in browsers means that anybody can view their actual values by inspecting the web browsers' elements.

For security reasons, you usually don't want to expose actual user IDs fetched from databases for unauthorized users, and one way to hide user IDs used in cookies can be to encrypt these cookie values.

Within C# ASP.NET Core, this can be done by using the DataProtection extension. In an MVC ASP.NET Core solution, this is done by initially registering this extension in the file "Startup.cs" as illustrated below:

 public class Startup
 {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();

            // Register support for DataProtection extension:
            services.AddDataProtection();
        }

       // ...

Furthermore, you can create a separate service class that implements this DataProtection extension, e.g. in the class and code illustrated below which can be put in a separate service layer (folder) in the application:

using System;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;

namespace TestApp.Services
{
    public class DataProtector
    {
        private IDataProtector CookieProtector()
        {
            var dataProtectionProvider = DataProtectionProvider.Create("TestApp");
            var protector = dataProtectionProvider.CreateProtector("Cookie.Encryption");
            return protector;
        }

        /// Create a user cookie and encrypt it to prevent the real value
        /// from being exposed on the client side (via the browser).
        public void EncryptUserId(HttpContext context, int userId)
        {
            string protectedUserId = CookieProtector().Protect(userId.ToString());
            context.Response.Cookies.Append("User", protectedUserId);
        }

        /// Receive the encrypted user cookie and decrypt it to be able to handle its
        /// real value on the server side.
        public int? ReadUserId(HttpContext context)
        {
            int userId;

            if (!string.IsNullOrEmpty(context.Request.Cookies["User"]))
            {
                string id = CookieProtector().Unprotect(context.Request.Cookies["User"]);
                userId = Convert.ToInt32(id);
                return userId;
            }
            return (int?)null;
        }

    }
}

Moreover, to be able to use this service during the rendering (preparation) of web pages via a controller, it can look like the following example where cookie decryption is applied before the page "Start" is presented to the end user:

using Microsoft.AspNetCore.Mvc;
using TestApp.Services; // Include the service layer to reach our data protector service.

namespace TestApp.Controllers
{
    public class HomeController : Controller
    {
        // Autowire our DataProtector service via dependency injection:
        public IActionResult Start(DataProtector protector)
        {
            // E.g. userId fetched from DB, but hard coded in this example for simplicity:
            int userId = 1;
            protector.EncryptUserId(HttpContext, userId);

            // ...

            return View();
        }

    }
}

When the above user ID has been assigned to a cookie and encrypted via the service mentioned above, the cookie value "1" will only be exposed as an encrypted value in the browser as you can se in the following screenshot:

Finally, when you want to decrypt the cookie value to be able to handle its actual value on the server side again, this can be done by calling the implemented service class like in the example below:

/// A method used in a suitable place on the server-side where we want to call our DataProtection service
/// via a dependency injection to decrypt our cookie back to its actual value.
public void HandleDecryptedCookieValue(DataProtector protector)
{
    // This will assign a userId variable with the decrypted cookie value:
    var userId = protector.ReadUserId(HttpContext);

    // Now do something with the decrypted userId...
}