AspNetCore.DataProtection.CustomStorage.Dapper.SQLServer 1.1.4

Prefix Reserved
dotnet add package AspNetCore.DataProtection.CustomStorage.Dapper.SQLServer --version 1.1.4
                    
NuGet\Install-Package AspNetCore.DataProtection.CustomStorage.Dapper.SQLServer -Version 1.1.4
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="AspNetCore.DataProtection.CustomStorage.Dapper.SQLServer" Version="1.1.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AspNetCore.DataProtection.CustomStorage.Dapper.SQLServer" Version="1.1.4" />
                    
Directory.Packages.props
<PackageReference Include="AspNetCore.DataProtection.CustomStorage.Dapper.SQLServer" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add AspNetCore.DataProtection.CustomStorage.Dapper.SQLServer --version 1.1.4
                    
#r "nuget: AspNetCore.DataProtection.CustomStorage.Dapper.SQLServer, 1.1.4"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package AspNetCore.DataProtection.CustomStorage.Dapper.SQLServer@1.1.4
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=AspNetCore.DataProtection.CustomStorage.Dapper.SQLServer&version=1.1.4
                    
Install as a Cake Addin
#tool nuget:?package=AspNetCore.DataProtection.CustomStorage.Dapper.SQLServer&version=1.1.4
                    
Install as a Cake Tool

Build and test Create Release Publish Nuget package

GitHub release GitHub issues GitHub pull requests

packages
AspNetCore.DataProtection.CustomStorage AspNetCore.DataProtection.CustomStorage
AspNetCore.DataProtection.CustomStorage.Dapper AspNetCore.DataProtection.CustomStorage.Dapper
AspNetCore.DataProtection.CustomStorage.Dapper.SQLServer AspNetCore.DataProtection.CustomStorage.Dapper.SQLServer
AspNetCore.DataProtection.CustomStorage.Dapper.PostgreSQL AspNetCore.DataProtection.CustomStorage.Dapper.PostgreSQL

GitHub

AspNetCore.DataProtection.CustomStorage

Persist ASP.NET Core Data Protection keys in a storage backend of your choice.

The base package lets you plug any backend into the Data Protection key ring by implementing a single, small interface. Ready-to-use SQL Server and PostgreSQL implementations (built on Dapper) are provided as separate packages.

Contents

Why

When an ASP.NET Core app runs on more than one node (containers, a web farm, autoscaling), every instance must share the same Data Protection keys; otherwise cookies, antiforgery tokens and anything else protected on one node cannot be read on another. This library keeps that key ring in a shared, durable store instead of the local filesystem.

Packages

Package Description
AspNetCore.DataProtection.CustomStorage Core abstractions. Bring your own backend by implementing IDataProtectionStorage.
AspNetCore.DataProtection.CustomStorage.Dapper Shared Dapper-based building blocks used by the database providers.
AspNetCore.DataProtection.CustomStorage.Dapper.SQLServer Ready-to-use SQL Server provider.
AspNetCore.DataProtection.CustomStorage.Dapper.PostgreSQL Ready-to-use PostgreSQL provider.

The database provider packages transitively depend on .Dapper and the core package, so installing a provider is all you need for the SQL Server or PostgreSQL scenario.

Requirements

  • .NET 10.0 or later

Installation

For SQL Server:

dotnet add package AspNetCore.DataProtection.CustomStorage.Dapper.SQLServer

For PostgreSQL:

dotnet add package AspNetCore.DataProtection.CustomStorage.Dapper.PostgreSQL

For a custom backend, install only the core package:

dotnet add package AspNetCore.DataProtection.CustomStorage

Quick start

SQL Server

using AspNetCore.DataProtection.CustomStorage.Dapper.SQLServer;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDataProtection()
    .PersistKeysWithDapperInSqlServer(
        builder.Configuration.GetConnectionString("DataProtection")!);

var app = builder.Build();

// Creates the keys table if it does not exist yet
// (InitializeTable defaults to true; see Configuration below).
app.Services.UseDapperDataProtection();

app.Run();

PostgreSQL

using AspNetCore.DataProtection.CustomStorage.Dapper.PostgreSQL;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDataProtection()
    .PersistKeysWithDapperInPostgreSQL(
        builder.Configuration.GetConnectionString("DataProtection")!);

var app = builder.Build();

app.Services.UseDapperDataProtection();

app.Run();

Note: the PostgreSQL provider enables Dapper's snake_case column mapping globally (Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true). Keep this in mind if the same application uses Dapper elsewhere.

Configuration

Both providers accept an optional Action<DapperDataProtectionConfig> to override the defaults:

builder.Services.AddDataProtection()
    .PersistKeysWithDapperInSqlServer(connectionString, config =>
    {
        config.SchemaName = "security";
        config.TableName  = "DpKeys";
        config.InitializeTable = false; // you manage the schema yourself
    });
Option Type Default (SQL Server) Default (PostgreSQL) Description
SchemaName string dbo public Schema that contains the keys table.
TableName string DataProtectionKeys data_protection_keys Name of the keys table.
InitializeTable bool true true When true, UseDapperDataProtection() creates the table (and its unique index) if it is missing.

UseDapperDataProtection() is an extension on IServiceProvider. Call it once at startup (app.Services.UseDapperDataProtection()): it validates the service registrations and, when InitializeTable is true, creates the table. If you set InitializeTable = false you are responsible for creating the schema (see below) before the app stores keys.

Database schema

When InitializeTable is enabled, the provider creates a table equivalent to the following.

SQL Server (dbo.DataProtectionKeys by default):

CREATE TABLE [dbo].[DataProtectionKeys](
    [Id]           [int] IDENTITY(1,1) NOT NULL,
    [InsertDate]   [datetime] NOT NULL DEFAULT getdate(),
    [FriendlyName] [nvarchar](256) NULL,
    [Xml]          [nvarchar](max) NOT NULL,
    CONSTRAINT [PK_DataProtectionKeys] PRIMARY KEY CLUSTERED ([Id] ASC)
);
CREATE UNIQUE NONCLUSTERED INDEX [IX_DataProtectionKeys_FriendlyName]
    ON [dbo].[DataProtectionKeys]([FriendlyName] ASC)
    WHERE [FriendlyName] IS NOT NULL;

PostgreSQL (public.data_protection_keys by default):

CREATE TABLE IF NOT EXISTS public.data_protection_keys (
    id            INTEGER GENERATED ALWAYS AS IDENTITY,
    insert_date   timestamp with time zone NOT NULL DEFAULT NOW(),
    friendly_name character varying(256) NULL,
    xml           text NOT NULL,
    CONSTRAINT pk_public_data_protection_keys PRIMARY KEY (id)
);
CREATE UNIQUE INDEX IF NOT EXISTS ix_public_data_protection_keys_friendly_name
    ON public.data_protection_keys (friendly_name ASC NULLS LAST);

The unique index on FriendlyName / friendly_name allows NULL values.

Custom storage backend

Reference AspNetCore.DataProtection.CustomStorage and implement IDataProtectionStorage:

using AspNetCore.DataProtection.CustomStorage;

public class MyDataProtectionStorage : IDataProtectionStorage
{
    public IEnumerable<DataProtectionKey> GetAll()
    {
        // load every stored key from your backend
    }

    public void Insert(DataProtectionKey key)
    {
        // persist a new key; key.FriendlyName must be unique when it is not null
    }
}

DataProtectionKey carries the two values the key ring needs:

  • FriendlyName (string?) — a name that must be unique when it is not null.
  • Xml (string, required) — the serialized key.

Register your implementation in DI and wire it up:

builder.Services.AddScoped<MyDataProtectionStorage>();

builder.Services.AddDataProtection()
    .PersistKeysToStorage<MyDataProtectionStorage>();

PersistKeysToStorage<TStorage>() resolves TStorage from a fresh dependency-injection scope on each read/write, so you must register it yourself (any lifetime works). If an Insert throws, it is wrapped in a KeyInsertException.

How it works

PersistKeysToStorage<TStorage>() registers a StorageWrapper<TStorage> as the Data Protection IXmlRepository. The wrapper adapts the framework's XML-element calls to the simpler IDataProtectionStorage contract (GetAll() / Insert()), resolving TStorage from a scoped service provider for every operation.

The database providers add a Dapper layer (IDbDataProtectionStorage) on top of that contract, implementing the raw SQL for SQL Server and PostgreSQL, plus schema initialization (InitializeDb()) and an async read path (GetAllAsync()).

Contributing

Issues and pull requests are welcome at the GitHub repository.

License

Licensed under the Apache License 2.0.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.4 109 7/23/2026
1.1.3 893 4/24/2026
1.1.2 110 4/20/2026
1.0.2 2,199 11/13/2024
1.0.1 182 10/29/2024
1.0.0 178 10/29/2024