An Introduction to Data Protection in ASP.NET Core

.NET Core MVC Cryptography
When building enterprise applications, developers are often faced with the problem of how to store sensitive user data, such as dates of birth, credit card numbers, or other personal information.

Storing a password in a secure manner is easy. Mash up the user's intended password with a SALT of some kind and preferably their unique numeric user ID, then hash the concatenated value with some avalanching algorithm like SHA1 and Bob's your uncle.

The problem with hashing is that it is irreversible, without creating a potentially massive rainbow table to map every possible variant of the user’s password against the known SALT and numeric ID. Hashing is therefore ideal for validating input, but useless for retrieving the original value programmatically.

Enter Data Protection. In this article, we will discuss how to use Data Protection with ASP.NET Core to protect and unprotect data and with user-specific keys to keep the protected data private. It is even possible to create time-limited protected data which expires after a certain duration.

See: docs.microsoft.com for an introduction to Data Protection.

I believe the best way to understand something is by doing. We will, together, create an ASP.NET Core web application and implement Data Protection. So, let’s get Visual Studio opened up and get going!

Set up our project



Open Visual Studio and create a new project. We want to select ASP.NET Core Web Application with the C# programming language. Give it a name MyDataProtectionWebApp and chose the MVC template. We want to use .NET Core 3.1 and as it is just going to run locally, we can disable “Configure for HTTPS”.

When the solution has been created, we can begin with installing our NuGet packages. Open the NuGet Package Manager and search for Microsoft.AspNetCore.DataProtection. Notice that this NuGet package has a bunch of dependencies within Microsoft.AspNetCore and Microsoft.Extensions namespaces. These will be installed automatically.

So, install the latest version of Microsoft.AspNetCore.DataProtection in your project and we’re good to go.

We need to add Data Protection as a service in our Startup.cs file.
Modify the ConfigureServices() method by adding:

services.AddDataProtection().SetApplicationName("My Data Protected Web App");


The use of the SetApplicationName() method is optional, but we are going to set a value now for the sake of clarity. The string we pass in here corresponds to the .ApplicationDiscriminator property of the DataProtectionOptions object we could instead configure in the AddDataProtection() method.

See also: docs.microsoft.com

Define a model and repository interface



A good place to start is to define public interfaces for repositories, models and generally anything within the “domain” of the system. In our case, that will be an IUser interface and an IUserRepository interface.


namespace MyDataProtectionWebApp.Interfaces
{
public interface IUser
{
string Name { get; }
System.DateTime DateOfBirth { get; }
}
}


To keep it simple, the user will be represented by two properties, a Name and a DateOfBirth. There will be also be a numeric user ID, but we won’t include this in our interface.


namespace MyDataProtectionWebApp.Interfaces
{
public interface IUserRepository
{
int AddUser(string name, System.DateTime date_of_birth);
IUser GetUser(int id);
}
}


The repository has a method for adding a user, which returns the new user ID, and a method for retrieving a user by that ID.

Create a user repository



There are many ways to skin a cat. Typically, we may wish to use a database to store user information, or some sort of external service. In this instance, we’ll create an in-memory user repository as we are just testing data protection.

Let’s create a class for our data repository.


using System;
using Microsoft.AspNetCore.DataProtection;
using MyDataProtectionWebApp.Interfaces;

namespace MyDataProtectionWebApp.Repositories
{
class UserRepository : IUserRepository
{
private readonly IDataProtectionProvider provider;

public UserRepository(IDataProtectionProvider provider)
{
this.provider = provider;
}

public int AddUser(string name, DateTime date_of_birth)
{
throw new NotImplementedException();
}

public IUser GetUser(int id)
{
throw new NotImplementedException();
}
}
}


Notice the parameter that we’ve added to the constructor here. This IDataProtectionProvider parameter will be populated by dependency injection in .NET Core (Praise be to the DI-Gods), using something like a KeyRingBasedDataProtector.

See also: docs.microsoft.com

Go back to Startup.cs and add another line to the ConfigureServices() method.

services.AddSingleton<IUserRepository, UserRepository>();


In principal, you should NOT do this in production systems! Singletons must be thread safe, and it’s generally not recommended to use this way of injecting into the DI-container. But since this is just for a local test, we’ll break those guidelines.

Create a users controller



Because we are going to be working with user information, let’s add a UsersController class to our project in the Controllers folder.


using Microsoft.AspNetCore.Mvc;
using MyDataProtectionWebApp.Interfaces;

namespace MyDataProtectionWebApp.Controllers
{
public class UsersController : Controller
{
private readonly IUserRepository repo;

public UsersController(IUserRepository repo)
{
this.repo = repo;
}

public IActionResult Index()
{
return View();
}
}
}


We’ll also create the CSHTML page so that the Index() method works properly.

Add a file Index.cshtml to the Views\Users folder.


@{
ViewData["Title"] = "Users";
}

This is my Users page.

@this.Model


Build and run your project in Debug mode. Navigate to the /Users/ page in the browser. If you put a breakpoint in the constructor of the UsersController, with any luck, you’ll notice that the repo parameter has been successfully populated through dependency injection, and you’ve got a repository with an appropriate data protection provider object.

Create an Action method to add a new user



To save time, we’re going to do this the quick and dirty way. When you understand the point of data protection, you’ll be able to adapt your solution accordingly.

Add the following Action method to your UsersController:


public IActionResult AddNewUser()
{
var id = this.repo.AddUser("John Smith", new DateTime(1990, 9, 9));

return View("Index", id);
}


This method does something very quick and dirty. It adds a user to our repository, with name “John Smith” and a date of birth 9th September 1990.

Store the user information in the repository



This is where we get into data protection. We are going to protect the date of birth as an encrypted string, so it won’t be stored in the repository as a plaintext string.

Firstly, let’s create a class that will store our encrypted data.


using System;
using Microsoft.AspNetCore.DataProtection;

namespace MyDataProtectionWebApp.Models
{
class ProtectedUser
{
private readonly IDataProtectionProvider provider;
private readonly int id;
private readonly string name;
private readonly string encrypted_date_of_birth;

internal ProtectedUser(IDataProtectionProvider provider, int id, string name, DateTime date_of_birth)
{
if (provider is null) throw new ArgumentNullException(nameof(provider));

this.provider = provider;
this.id = id;
this.name = name;

var protector = this.GetProtector();

this.encrypted_date_of_birth = protector.Protect(date_of_birth.ToString());
}

private IDataProtector GetProtector()
{
return provider.CreateProtector(this.GetType().FullName, id.ToString());
}
}
}


We are saving the data protection provider as a private field, but in a production solution wouldn’t necessarily do this.

The .Protect(…); method of the protector encrypts the date of birth string. In this way, the “sensitive” data stored in your model is not plain text.

Next, we need to implement the AddUser method in our UserRepository class.


private readonly ConcurrentDictionary<int, ProtectedUser> users = new ConcurrentDictionary<int, ProtectedUser>();

public int AddUser(string name, DateTime date_of_birth)
{
int id = this.users.Count + 1;

var user = new ProtectedUser(this.provider, id, name, date_of_birth);

while (!this.users.TryAdd(id, user))
{
user = new ProtectedUser(this.provider, ++id, name, date_of_birth);
}

return id;
}


It is important to use a ConcurrentDictionary here. This is what makes our repository thread safe.

Build and run your project. Navigate to the /Users/AddNewUser/ page in the browser. You should now see “This is my Users page. 1”

The “1” here indicates the user ID of the user we’ve created. If you keep refreshing the page, this ID should increase incrementally, as new objects are being added to the concurrent dictionary in the repository class.

Put a break point in your repository and inspect the concurrent dictionary. You’ll see that the encrypted date of birth on each value in the collection is a completely incomprehensible string. Great!

Retrieving the user and decoding the data



To keep things simple, we’ll just expand our ProtectedUser class to implement IUser and do the necessary “unprotection” of the date of birth.


// existing using directives
using MyDataProtectionWebApp.Interfaces;

namespace MyDataProtectionWebApp.Models
{
class ProtectedUser : IUser
{
// existing fields and methods

public string Name => this.name;

public DateTime DateOfBirth
{
get
{
var protector = this.GetProtector();

var val = protector.Unprotect(this.encrypted_date_of_birth);

return DateTime.Parse(val);
}
}

public override string ToString()
{
return this.Name + ", born " + this.DateOfBirth.ToLongDateString();
}
}
}


Now to implement the GetUser() method in the UserRepository.


public IUser GetUser(int id)
{
if (this.users.TryGetValue(id, out var user))
{
return user;
}
else return null;
}


Also, we’ll add a method to our UsersController just to fetch the user after we add it.


public IActionResult GetUser()
{
var user = this.repo.GetUser(1);

return View("Index", user);
}


Test the project



Build and run your project. Navigate to /Users/AddNewUser/ in the browser to add the user to the repository. Then, navigate to /Users/GetUser/.

You should see the text:
“This is my Users page. John Smith, born 09 September 1990”.

Summary



Data Protection helps you to encrypt data on a per-application, per-type, per-user basis.

We specified the Application Name in Startup.cs ConfigureServices() method, and we created a Data Protector based on the fully qualified type name of the data class, along with the unique user ID present in the data class.

In production, you’d also want to use a “secret” code, for example, taken from Azure Key Vault or similar, so that data decryption would be impossible without it.

There are also other ways to protect data, including by using password-protected certificates, but that’s a little too much to explain in this blog post 😊

I hope you find this example useful! Happy coding, and please, don’t forget to support me!
Hey you! I need your help!

Thanks for reading! All the content on this site is free, but I need your help to spread the word. Please support me by:

  1. Sharing my page on Facebook
  2. Tweeting my page on Twitter
  3. Posting my page on LinkedIn
  4. Bookmarking this site and returning in the near future
Thank you!