Data Protection for ASP.NET Core Developers and Duende IdentityServer
An essential part of securing ASP.NET Core applications while maintaining the capabilities to scale out to meet user demand is Data Protection.
In this post, we’ll discuss data protection, how to implement it, how to configure data protection options, and some choices you may want to consider when building your applications. We’ll also explain how this relates to our Duende IdentityServer product offering.
What is Data Protection?
Data Protection, a set of .NET cryptographic APIs, is a friendlier approach to security used by ASP.NET Core and other libraries to protect data from unauthorized access. Its foundational tenets include authenticity, integrity, and protection against tampering, all designed with the developer and third-party library author in mind. In short, it’s the go-to mechanism for securing sensitive data in your applications, making you feel confident in your data security measures.
You can access Data Protection through the following NuGet packages:
Microsoft.AspNetCore.DataProtection
Microsoft.AspNetCore.DataProtection.Abstractions
Microsoft.AspNetCore.DataProtection.Extensions
Microsoft.AspNetCore.Cryptography.KeyDerivation
For folks still working with ASP.NET 4.x apps, you may also need to add Microsoft.AspNetCore.DataProtection.SystemWeb
package to access the <machineKey>
element commonly found in the web.config
of legacy applications.
ASP.NET Core accesses these packages implicitly through the Microsoft.NET.Sdk.Web,
so you typically don’t need to add them manually in your package references.
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
Let’s look at how to use these APIs directly, although, as you’ll see later, it’s a feature of ASP.NET Core you typically don’t have to use directly.
In a new ASP.NET Core minimal API project, you can use Data Protection by pasting the following code. This code is a practical example of using Data Protection in your application, and it will help you understand the process better.
using Microsoft.AspNetCore.DataProtection;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication();
builder.Services.AddDataProtection();
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.MapGet("/secure",
(string token, IDataProtectionProvider dpp) =>
{
var dp = dpp.CreateProtector("secure");
return dp.Protect(token);
});
app.MapGet("/unsecure",
(string token, IDataProtectionProvider dpp) =>
{
var dp = dpp.CreateProtector("secure");
return dp.Unprotect(token);
});
app.Run();
You’ll notice a few essential elements to the C# codebase.
- We first need to register Data Protection with the collection of our services using
AddDataProtection
registration method. - We have endpoints for
secure
andunsecured
that use theIDataProtectorProvider.
- We need to create an instance of
IDataProtector
using theCreateProtector
method and pass apurpose
. The specific underlying implementation ofIDataProtector
determines how to create and store keys logically based on the purpose. We create anIDataProtector
using the provider to ensure we get the correct protector for the intended purpose.
Calling our endpoints, we can now secure and unsecure the passed token using the registered Data Protection APIs. If you’re using the JetBrains Rider IDE, you can use the following HTTP script to verify everything is working. Be sure to update the URLs to match your application’s URL.
## Secure the token
GET https://localhost:7039/secure?token=Khalid
> {%
client.global.set("token", response.body);
%}
### Unsecure the token
GET https://localhost:7039/unsecure?token={{token}}
> {%
client.global.clear("token")
client.test("token was successfully unsecured", function () {
client.assert(response.body === "Khalid", "Response was not expected value");
console.log(response.body)
});
%}
Great. You’ve just implemented the use of Data Protection, but as you’ll see in the next section, the critical elements of Data Protection happen during configuration.
How to implement Data Protection in ASP.NET Core?
While using the Data Protection APIs directly is possible, you’re more likely to use Data Protection through ASP.NET Core Authentication. A call to AddAuthentication
adds Data Protection to your ASP.NET Core applications.
public static AuthenticationBuilder AddAuthentication(this IServiceCollection services)
{
ArgumentNullException.ThrowIfNull(services);
services.AddAuthenticationCore();
services.AddDataProtection();
services.AddWebEncoders();
services.TryAddSingleton(TimeProvider.System);
#pragma warning disable CS0618 // Type or member is obsolete
services.TryAddSingleton<ISystemClock, SystemClock>();
#pragma warning restore CS0618 // Type or member is obsolete
services.TryAddSingleton<IAuthenticationConfigurationProvider, DefaultAuthenticationConfigurationProvider>();
return new AuthenticationBuilder(services);
}
What is more important, and what we find here at Duende Software, is that folks typically forget to configure their data protection for production deployment scenarios. This step is crucial and should not be overlooked. It ensures your data is protected in all scenarios, making you feel responsible and proactive in your approach to data security.
Consider three crucial implementation details for Data Protection users, which apply to most ASP.NET Core application developers. These details are vital as they ensure the proper functioning of your application in a distributed environment like Windows Azure, AWS, Google Cloud, or another production environment as you scale past a single host machine. The following method calls should appear in your application setup code.
- We have a call to persist Data Protection keys to a data storage mechanism.
- We have a call to protect the keys using a secure method.
- We have a call to set the application name that creates a partition for your application.
Look at the example code you might find in a correctly configured application.
builder.Services
.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo("keys"))
.ProtectKeysWithCertificate(thumbprint: "<certificate thumbprint>")
.SetApplicationName("DataProtectionExample");
Setting these values is essential, as they allow your application to work in a distributed environment like Windows Azure, AWS, Google Cloud, or another production environment as you scale past a single host machine.
While this is one permutation of setting up Data Protection for an ASP.NET Core application, as you’ll see in the next section, there are many options, and we’ll list some of the more popular ones.
What are my Data Protection options?
As we outlined, data protection involves three key elements: persisting keys, protecting keys, and setting the application name. These elements are the backbone of data protection and understanding their role is crucial for implementing a robust security strategy in your ASP.NET Core application. In this section, we’ll list some options for persisting and safeguarding keys.
What are my cloud options for Data Protection?
Let’s start with the most popular cloud environments, such as Microsoft Azure and Amazon Web Services. Major cloud providers offer NuGet Data Protection packages, allowing you to store your keys safely and securely.
For Microsoft Azure users, you’ll want to add the following packages to your ASP.NET Core applications.
dotnet add package Azure.Identity
dotnet add package Azure.Extensions.AspNetCore.DataProtection.Keys
dotnet add package Azure.Extensions.AspNetCore.DataProtection.Blobs
These packages allow the ASP.NET Core application to generate a Data Protection key and store it in Azure Blob Storage if the keys are not already in the storage container. Then, using Azure KeyVault, the application can limit access to those keys to the Azure-provided App Service’s identity.
builder.Services
.AddDataProtection()
.PersistKeysToAzureBlobStorage("<connectionString", "<containerName>", "<blobName>")
.ProtectKeysWithAzureKeyVault(new Uri("<key-iD>"), new DefaultAzureCredential())
.SetApplicationName("DataProtectionExample");
The DefaultAzureCredential
is an implementation that uses the Azure application’s identity to determine access to the KeyVault.
Again, you’ll need additional work in your Microsoft Azure environment to set up the KeyVault access, App Service, and Blob Storage. After that, you can use Data Protection properly in your ASP.NET Core applications.
Let’s look at AWS and ASP.NET Core applications. First, you’ll need the following NuGet packages.
dotnet add package Amazon.AspNetCore.DataProtection.SSM
In the case of AWS, the keys only persist in the AWS Systems Manager’s Parameter Store.
builder.Services.AddDataProtection()
.PersistKeysToAWSSystemsManager("/my-app")
.SetApplicationName("my-app");
From here, all your instances of the AWS-hosted application will begin sharing the keys, making scaling out possible.
Let’s look at self-hosted situations and some options to consider when persisting and protecting your keys.
What are my self-hosted options for Data Protection?
While we’re using the term “self-hosted” in this section, these recommendations can also apply to containerized deployments supported by many other popular hosting platforms.
You have a few choices for persisting keys based on your operating system, with Windows offering a few more options.
- You can persist keys to the Registry on Windows Hosts
- You can persist keys to a secure directory (making sure the path exists on all machines)
- You can persist to data storage such as SQL Server, MongoDB, Redis, etc.
- You can use ephemeral data protection (in memory)
These persistence mechanisms are explicit calls in your .NET code. You can also manually persist keys to the host through administrative actions such as copying a file, adding a new certificate to your certificate store, inserting a database record, or setting the registry keys yourself.
The popular package for persisting keys allows developers to use Entity Framework Core to write and read keys to any database supported by the popular library.
dotnet add package Microsoft.AspNetCore.DataProtection.EntityFrameworkCore
Next, you’ll need a DbContext
instance that implements the IDataProtectionKeyContext,
which enforces the implementation of a specific DbSet
.
public class Database : DbContext, IDataProtectionKeyContext
{
public DbSet<DataProtectionKey> DataProtectionKeys
=> Set<DataProtectionKey>();
}
The DataProtectionKey
entity creates a table with three columns: Id
, FriendlyName
, and Xml
. You can use Entity Framework migrations to add this table on your next deployment or create this table using your current database migration strategy.
Protecting Data Protection keys is an added layer of security that you should consider. You can use the provided options in ASP.NET Core or implement your own. We recommend sticking with vetted implementations in most cases. Options for protecting keys include:
- Encrypt keys using machine-native encryption using the Windows DPAPI. DPAPI ensures that only the current Windows account can access the keys.
- You can use Windows CNG DPAPI, which encrypts keys with Windows CNG DPAPI before being persisted to storage. The keys will be decryptable by the current Windows user account and are available on Windows 8 / Windows Server 2012 or later versions.
- Certificates can protect keys, which is most useful for users as the method allows developers to switch between Windows, macOS, and Linux hosts. You can install certificates on the host environment’s certificates store or load them into memory using the
X509Certificate2
class.
There are many options, and you’ll need to determine which are best suited for your current situation. Our discussion forum has an active community so that you can discuss your use case with other Duende Software customers.
To get a comprehensive list of all the Data Protection options available to ASP.NET Core developers. We recommend reading the official Microsoft Documentation for the latest options.
How does Duende IdentityServer use Data Protection?
Duende IdentityServer relies on Data Protection to secure your Identity Access Management solution in several essential ways.
First, users of our automatic key management features use data protection to protect signing keys at rest, increasing security around token creation and signing.
Since Duende IdentityServer also uses cookies, all cookies at the identity provider and clients go through the Data Protection APIs to secure stored information. Data Protection ensures that cookies can be trusted, even though they are passed back and forth between a user and the server.
We also use data protection for server-side sessions, persisted grant stores containing refresh and reference tokens, and more places. All to add more layers of security.
When solutions pass payloads between UI elements within IdentityServer, the payload data is passed through the Data Protection APIs, which ensures the integrity of any information being used to act on behalf of the user.
We’ve compiled a list of common problems people encounter when using IdentityServer, and we find that they typically involve Data Protection. Our documentation includes a comprehensive list of data protection issues for folks implementing Duende IdentityServer in their solutions.
Conclusion
As you can see, Data Protection is an essential part of building modern ASP.NET Core applications. While it’s relatively straightforward to implement, it can be frustrating for development teams to miss Data Protection configuration. By checking your application setup code, you can help save countless issues from finding their way into production and make your deployments smoother.
You can find more information about data protection and how it relates to Duende IdentityServer in our documentation, and remember to check out our online community for more discussions around data protection and other security topics.