Installation
Docker (recommended)
Pull and run the pre-built image:
docker run -p 8080:8080 \
-e Storage__ConnectionString="your-connection-string" \
-e Issuer="https://auth.example.com" \
drawboardci/authagonal
Docker Compose
For local development with Azurite (Azure Storage emulator):
services:
azurite:
image: mcr.microsoft.com/azure-storage/azurite
ports:
- "10000:10000"
- "10001:10001"
- "10002:10002"
authagonal:
build: .
ports:
- "8080:8080"
environment:
- Storage__ConnectionString=DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;TableEndpoint=http://azurite:10002/devstoreaccount1;
- Issuer=http://localhost:8080
depends_on:
- azurite
docker compose up
Building from Source
Prerequisites
- .NET 10 SDK
- Node.js 24+
Build
# Build everything
dotnet build
# Build the login SPA
cd login-app
npm ci
npm run build
# Run the server
dotnet run --project src/Authagonal.Server
Docker Build
# Server image (multi-stage: builds SPA + .NET in one image)
docker build -t authagonal .
# Migration tool
docker build -f Dockerfile.migration -t authagonal-migration .
As a Library (NuGet)
Reference the Authagonal packages in your own ASP.NET Core project:
<PackageReference Include="Authagonal.Server" Version="x.y.z" />
<PackageReference Include="Authagonal.AzureProvider" Version="x.y.z" />
The storage provider package is pluggable: Authagonal.AzureProvider for Azure Table Storage (the default AddAuthagonal() wiring), or Authagonal.AwsProvider for DynamoDB / S3 / Secrets Manager, see AWS backend below.
Then compose it into your Program.cs:
builder.Services.AddSingleton<IAuthHook, MyAuditHook>(); // Custom hook
builder.Services.AddSingleton<IEmailService, MyEmailService>(); // Custom email
builder.Services.AddAuthagonal(builder.Configuration);
var app = builder.Build();
app.UseAuthagonal();
app.MapAuthagonalEndpoints();
app.MapFallbackToFile("index.html");
app.Run();
See Extensibility for all override points and demos/custom-server/ for a complete example.
The built-in Resend sender activates automatically when Email:ResendApiKey and Email:SenderEmail are configured, no service registration needed. Without any IEmailService, verification and password-reset emails are silently discarded, and because login requires a confirmed email by default, self-registered users can never sign in (UseAuthagonal logs a warning at startup). Either set the Email:* keys, register your own IEmailService before AddAuthagonal(), or list your domains in Auth:AutoConfirmEmailDomains to skip verification (dev/test only). See Configuration → Email.
AWS backend
To run on AWS instead of Azure, reference Authagonal.AwsProvider and register the AWS bundle before AddAuthagonal(), those registrations are what make AddAuthagonal() skip its Azure Table Storage wiring:
using Authagonal.AwsProvider;
builder.Services.AddAuthagonalAwsStorage(
dynamoDb, // IAmazonDynamoDB — required
secretsManager, // IAmazonSecretsManager — optional; replaces the plaintext ISecretProvider
s3, // IAmazonS3 — optional; used for DataProtection keys
"my-auth-keys-bucket"); // S3 bucket for the DataProtection key ring
builder.Services.AddAuthagonal(builder.Configuration);
The DynamoDB tables mirror the Azure layout one-for-one and are ensured on startup (idempotent, a no-op when they’re already provisioned by Terraform). Credentials resolve via the standard AWS chain (env / EC2 instance role / IRSA), so there is no connection-string-vs-managed-identity split, no Storage:* configuration is needed.
⚠️ S3 DataProtection keys. Without an S3 client + bucket, the ASP.NET Core Data Protection key ring is held in memory, fine for a single node in dev, but cookies and antiforgery tokens break on restart and across nodes in production. Always pass the S3 client and bucket for a production AWS deployment.
Login SPA (npm)
The login UI is published as an npm package for customization:
npm install @authagonal/login
The package ships compiled JS and CSS, import components and styles directly in your own React app. See Custom Server for a full walkthrough.
Production security checklist
Before exposing Authagonal to real traffic, confirm the following. Each item is detailed on the Configuration page.
- Run behind a TLS-terminating proxy. Authagonal must sit behind a reverse proxy / ingress that terminates TLS. The session cookie uses
SecurePolicy = SameAsRequestand HSTS is only emitted on HTTPS, so the proxy must forwardX-Forwarded-Proto: https. SetForwardedHeaders:KnownNetworks(orKnownProxies) to your ingress / pod CIDR so the client IP and scheme cannot be spoofed;ForwardedHeaders:ForwardLimitdefaults to1(trust only the last hop). - Set
SecretProvider:VaultUri. The default secret provider is plaintext: without Key Vault, upstream OIDC client secrets and TOTP / MFA seeds are stored in cleartext in Table Storage (and in backups). Configure Key Vault for any production deployment. - Lock down the admin API.
AdminApi:Enableddefaults to true. The admin scope (AdminApi:Scope, defaultauthagonal-admin) grants full management and user impersonation. Network-restrict the/api/v1/*admin routes and tightly control who is issued the admin scope, or setAdminApi:Enabled = falseif unused. - Protect internal endpoints. Set
Cluster:Secretso the internal/_internal/backchannel-logoutendpoint requires theX-Cluster-Secretheader (compared in constant time). When unset, it accepts only loopback / private (RFC 1918 / link-local / ULA) source IPs, make sure your forwarded-headers trust is configured so an external caller can’t appear internal. - Encrypt backups. With the plaintext secret provider, backups contain secrets. The
SigningKeystable is excluded from backups by default; if you opt in viaBackup:IncludeSigningKeys, the backup target must be encrypted at rest. See Backup & Restore.
Migration Tool
For migrating from Duende IdentityServer + SQL Server:
docker run authagonal-migration -- \
--Source:ConnectionString "Server=...;Database=...;" \
--Target:ConnectionString "DefaultEndpointsProtocol=https;..." \
[--DryRun true] \
[--MigrateRefreshTokens true]
See Migration for details.