Authagonal.SqlProvider 0.21.0

dotnet add package Authagonal.SqlProvider --version 0.21.0
                    
NuGet\Install-Package Authagonal.SqlProvider -Version 0.21.0
                    
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="Authagonal.SqlProvider" Version="0.21.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Authagonal.SqlProvider" Version="0.21.0" />
                    
Directory.Packages.props
<PackageReference Include="Authagonal.SqlProvider" />
                    
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 Authagonal.SqlProvider --version 0.21.0
                    
#r "nuget: Authagonal.SqlProvider, 0.21.0"
                    
#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 Authagonal.SqlProvider@0.21.0
                    
#: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=Authagonal.SqlProvider&version=0.21.0
                    
Install as a Cake Addin
#tool nuget:?package=Authagonal.SqlProvider&version=0.21.0
                    
Install as a Cake Tool

Authagonal.SqlProvider

Self-hosted SQL storage for AuthagonalPostgreSQL for production, SQLite for a single file with no server at all. No cloud account, no emulator, no managed service.

Implements the full Authagonal.Core.Stores surface, the clustering seams (ILeaseProvider, IClusterEventBus), and DataProtection key-ring persistence — the same shape as Authagonal.AzureProvider and Authagonal.AwsProvider, so switching backends is a wiring change.

Quick start

// PostgreSQL — call BEFORE AddAuthagonal
builder.Services.AddAuthagonalPostgres("Host=db;Database=authagonal;Username=auth;Password=…");
builder.Services.AddAuthagonal(builder.Configuration);
// SQLite — one file, nothing to run
builder.Services.AddAuthagonalSqlite("Data Source=authagonal.db");
builder.Services.AddAuthagonal(builder.Configuration);

Tables are created on startup if absent (every statement is IF NOT EXISTS, so it is safe to race across pods and a no-op against a schema you provisioned yourself).

This package is not a dependency of Authagonal.Server — referencing the server library never pulls Npgsql or the SQLite native binaries into an Azure- or AWS-only application. Reference it explicitly when you want a SQL backend, exactly as with Authagonal.AwsProvider.

Which one

PostgreSQL SQLite
Multiple pods / HA yes no — one writer by construction
Leader election, cluster event bus UseSql() in-process (the default) is correct
Ops a server to run and back up a file to back up
Good for production self-hosting quick start, embedded hosts, CI, small deployments

Layout

Every table is the same generic shape, which is what keeps the key scheme identical to Azure Table Storage's PartitionKey/RowKey and DynamoDB's HASH/RANGE:

CREATE TABLE "Users" (
    pk         TEXT   COLLATE "C" NOT NULL,   -- partition key
    sk         TEXT   COLLATE "C" NOT NULL,   -- sort key
    data       TEXT,                          -- the document (JSON, possibly encrypted)
    attrs      JSONB  NOT NULL DEFAULT '{}',  -- promoted, queryable fields
    version    BIGINT NOT NULL DEFAULT 0,     -- optimistic concurrency
    expires_at TEXT   COLLATE "C",            -- optional TTL
    PRIMARY KEY (pk, sk)
);

Table names match the other backends one-for-one, so a backup taken on Azure or AWS restores here without renaming anything.

COLLATE "C" is load-bearing. The key scheme is byte-ordinal throughout — prefix bounds, the env-partition range, the grant expiry sweep, keyset paging — and a database created with a linguistic collation (en_US.UTF-8 and ICU locales are the common defaults) orders punctuation and case differently. Those scans would then silently return the wrong rows: expired grants stop being reaped, prefix search misses matches. Pinning the collation per column makes the layout independent of how the database was created. The test suite runs against an ICU-collated database on purpose.

Concurrency

The operations an auth server cannot get wrong are each a single statement — no read-modify-write window, no explicit transaction, no lock held across a round trip:

Guarantee Mechanism
Authorization code / MFA challenge / OIDC state redeemable once DELETE … RETURNING
Refresh rotation marks consumed exactly once UPDATE … WHERE consumedAt IS NULL
Lockout counter loses no increments UPDATE … WHERE version = @v, re-read and retry
SAML assertion replay detected INSERT … ON CONFLICT DO NOTHING
At most one lease holder INSERT … ON CONFLICT DO UPDATE … WHERE expired OR mine

TTL

Neither backend expires rows on its own the way DynamoDB TTL does, so SqlExpiryReaper (registered automatically) sweeps the transient tables — SAML replay ids, OIDC state, MFA challenges, upstream refresh tokens, the revocation list. Those rows are already ignored on read once expired; the sweep is space reclamation, not correctness. Grant expiry is deliberately not handled there: grants span three tables that must be cleaned together with their tombstones, so IGrantStore.RemoveExpiredAsync stays the single owner.

Encryption at rest

The IFieldCipher / IIndexTokenizer seams work exactly as on the other backends. Register them before AddSqlStorage and the user document is encrypted and lookup keys become blind-index tokens; leave them out and the layout is plaintext. Rows written before you turned encryption on keep resolving, and IUserStore.ReindexUserAsync backfills them — so it can be switched on without downtime. For key material, pair with the HashiCorp Vault Transit signer.

Clustering (PostgreSQL)

builder.Services.AddAuthagonal(builder.Configuration, clustering =>
    clustering.UseSql(dataSource));      // leadership + event bus
    // or clustering.UseSqlBus(dataSource) on nodes that must not contend for leadership

Leadership is a conditional-upsert lease row; the event bus is an append-only log each node polls. LISTEN/NOTIFY would be lower-latency but needs a dedicated long-lived connection per node and drops what a disconnected listener missed — polling a durable log keeps the at-least-once delivery guarantee identical to the other backends.

Custom dialects

ISqlDialect is small: a connection, the DDL, a JSON accessor, and how to qualify a table name. Everything else is SQL both engines accept verbatim. Implement it and pass your own SqlDataSource to AddAuthagonalSqlStorage to target another engine.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  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
0.21.0 37 7/30/2026