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 groups.
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 VoidResult<ForbiddenError> CreateOrder(CreateOrderRequest request) { var userId = currentUser.Id; var tenantId = currentUser.TenantId; if (!currentUser.Authorization.HasPermission("orders.create")) return ForbiddenError.MissingPermission("orders.create"); // ... return VoidResult<ForbiddenError>.Success(); }}Packages
Section titled “Packages”| Package | Role |
|---|---|
Pragmatic.Identity |
SystemUser, identity options, claim mapping; no ASP.NET Core dependency |
Pragmatic.Identity.AspNetCore |
ClaimsPrincipal → ICurrentUser, header-based development identity |
Pragmatic.Identity.Local |
Self-contained local identity: signup, login, password reset (LocalIdentityPackage) |
Pragmatic.Identity.Local.Jwt |
JWT issuing and bearer authentication: UseJwtAuthentication() |
Pragmatic.Identity.Oidc |
Bearer tokens from any external OpenID Connect provider: UseOidcAuthentication() |
Pragmatic.Identity.Keycloak |
Keycloak realm roles, plus an admin client for provisioning: UseKeycloakAuthentication() |
Pragmatic.Identity.Persistence |
EF Core stores for roles, groups and permissions, with temporal assignments |
Pragmatic.Identity.Auditing |
Failed logins and lockouts on the Audit trail, pseudonymised: AddIdentitySecurityAuditing() |
ICurrentUser itself lives in Pragmatic.Abstractions, so a library can consume it without any of
these.
Installation
Section titled “Installation”dotnet add package Pragmatic.Identitydotnet add package Pragmatic.Identity.Local.Jwt # optional: JWT authenticationQuick Start
Section titled “Quick Start”// JWT from the Jwt section of the configuration: Key, Issuer, Audienceapp.UseJwtAuthentication();
// 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.
- Users: a plain entity marked
[PragmaticUser]; the generator owns its identity members and EF configuration. - Persistence: EF stores for roles, groups and permissions, with temporal assignments. There is no JIT provisioning: creating a local record on first external login is not implemented (see Persistence).
- Authorization integration: feeds Authorization’s permission resolution.
Status
Section titled “Status”Functional within 1.0.0-alpha: ICurrentUser, local identity, JWT, and EF-backed persistence. See
the roadmap.
| Concepts | ICurrentUser model, claim mapping, accessors, the permission chain |
| Getting Started | Wire authentication, consume the current user |
| Packages | The core 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, external identities, [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).
