Pragmatic.Identity
Identity and authentication for the Pragmatic.Design ecosystem — a structured ICurrentUser that works
in HTTP requests, background jobs, and tests, layering up to database-backed identity with temporal
roles and JIT provisioning.
The Problem
Section titled “The Problem”ASP.NET Core’s ClaimsPrincipal is a transport object, not a domain abstraction. Every service that
needs the current user repeats the same pattern: resolve IHttpContextAccessor, null-check
HttpContext, navigate claims with magic-string URIs, and special-case each provider. Background jobs
have no HttpContext at all, so the whole pattern fails outside HTTP.
// Without Pragmatic.Identity: magic strings, fragile, HTTP-onlyvar userId = principal?.FindFirst("sub")?.Value ?? principal?.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value;The Solution
Section titled “The Solution”Replace ClaimsPrincipal with a structured ICurrentUser (identity + authorization + authentication
sub-objects). Claim mapping is centralized and configurable; the same code runs across HTTP
(ClaimsPrincipalUserAccessor), background jobs (SystemUser), and tests — unchanged. Dev vs
production auth switches by configuration (HTTP headers in dev, JWT in production).
public class OrderService(ICurrentUser currentUser){ public Result CreateOrder(CreateOrderRequest request) { var userId = currentUser.Id; var tenantId = currentUser.TenantId; if (!currentUser.Authorization.HasPermission("orders.create")) return Result.Failure(new ForbiddenError()); // ... }}Packages
Section titled “Packages”| Package | Role |
|---|---|
Pragmatic.Identity | ICurrentUser, claim mapping, accessors (HTTP / system / test) |
Pragmatic.Identity.Local | Self-contained local identity: signup, login, password reset |
Pragmatic.Identity.Local.Jwt | JWT bearer authentication |
Pragmatic.Identity.Persistence | EF-backed identity stores, temporal roles, JIT provisioning |
Installation
Section titled “Installation”dotnet add package Pragmatic.Identitydotnet add package Pragmatic.Identity.Local.Jwt # optional: JWT authenticationQuick Start
Section titled “Quick Start”// config-driven auth: JWT when a signing key is present, NoOp header-auth otherwiseapp.UseJwtAuthentication(jwt => jwt.SigningKey = app.Configuration["Jwt:Key"]!);
// consume the current user anywhere — no IHttpContextAccessor, no magic stringspublic class Handler(ICurrentUser user) { /* user.Id, user.TenantId, user.Authorization.HasPermission(...) */ }Full walkthrough: Getting Started.
What you get
Section titled “What you get”ICurrentUser— identity, authorization, authentication sub-objects; multi-value claims, tenant, impersonation; works in HTTP, jobs, and tests.- Local identity — signup/login/password-reset actions (
Pragmatic.Identity.Local). - JWT — bearer authentication with configurable issuer/audience/expiry.
- Persistence — EF stores, temporal roles, SCIM-aware JIT provisioning,
[PragmaticUser]generation. - Authorization integration — feeds Authorization’s permission resolution.
Status
Section titled “Status”ICurrentUser, local identity, JWT, and EF-backed persistence are functional within the 0.8 preview.
See the roadmap.
| Concepts | ICurrentUser model, claim mapping, accessors, the permission chain |
| Getting Started | Wire authentication, consume the current user |
| Packages | The five packages and how they layer |
| JWT Authentication | Bearer tokens, issuer/audience/expiry, signing keys |
| Dev Authentication | Header-based auth for development and tests |
| Persistence | EF stores, temporal roles, JIT provisioning, [PragmaticUser] |
| Common Mistakes | The most frequent identity pitfalls |
| Troubleshooting | Problem/solution guide |
Requirements
Section titled “Requirements”- .NET 10.0+
License
Section titled “License”Part of the Pragmatic.Design ecosystem — see Licensing. Pragmatic.Identity is licensed under the PolyForm Small Business 1.0.0 license (free for small businesses; commercial license above the threshold).