Package Architecture
The Pragmatic.Identity stack is split into five packages, each with a clear responsibility and minimal dependencies. This document describes each package in detail.
Layer Diagram
Section titled “Layer Diagram”Layer 0 (Abstractions) Layer 1 (Core) Layer 2 (ASP.NET) Layer 3 (Features)Pragmatic.Abstractions --> Pragmatic.Identity --> Identity.AspNetCore --> Identity.Local.Jwt ICurrentUser SystemUser ClaimsPrincipalUser JwtTokenGenerator IUserAuthorization IdentityOptions HeaderUserMiddleware UseJwtAuthentication IAuthenticationContext ClaimsPermissionChecker NoOpAuthHandler PrincipalKind IdentityRecord PragmaticPermHandler AnonymousUser
Identity.Local RegisterUser LoginUser BcryptHasher
Pragmatic.Authorization ------------------------------------------> Identity.Persistence CachedPermissionResolver EfRolePermissionStore IPermissionProvider EfGroupRoleStore IRolePermissionStore JitProvisioningPackage Details
Section titled “Package Details”1. Pragmatic.Identity
Section titled “1. Pragmatic.Identity”NuGet: Pragmatic.Identity
Dependencies: Pragmatic.Abstractions
ASP.NET Core: No
EF Core: No
The core package that provides identity primitives without any web framework dependency. Use this package when you need identity concepts in a library or non-ASP.NET project.
Key Types
Section titled “Key Types”| Type | Description |
|---|---|
SystemUser | Singleton ICurrentUser for background jobs and system operations. Always authenticated with FullAccessUserAuthorization. |
IdentityOptions | Configures which claim types map to user ID, display name, role, permission, and tenant. |
IdentityRecord | Abstract base class for identity provider records (local, Keycloak, etc.). Contains ExternalIdentityKey, ProvisionSource, IsActive, LastLoginAt, plus IAuditable fields. |
ClaimsPermissionChecker | Wraps ICurrentUser.Authorization as async IPermissionChecker for consumers that require the async interface. |
ProvisionSource | Enum: Manual, Jit, Scim. |
When to Reference
Section titled “When to Reference”- You are building a library that depends on
ICurrentUserbut not ASP.NET Core. - You need
SystemUserfor background job contexts. - You are implementing a custom identity provider that extends
IdentityRecord.
2. Pragmatic.Identity.AspNetCore
Section titled “2. Pragmatic.Identity.AspNetCore”NuGet: Pragmatic.Identity.AspNetCore
Dependencies: Pragmatic.Identity, ASP.NET Core framework reference
ASP.NET Core: Yes
EF Core: No
The ASP.NET Core bridge. Maps HttpContext.User (a ClaimsPrincipal) to the Pragmatic ICurrentUser interface. Also provides development-time authentication tools.
Key Types
Section titled “Key Types”| Type | Description |
|---|---|
ClaimsPrincipalUserAccessor | Scoped ICurrentUser that reads from HttpContext.User. Claim types are configurable via IdentityOptions. Normalizes long-form claim URIs to short names (role, permission, tenant_id, sub, name). |
ClaimsAuthenticationContext | Reads IAuthenticationContext from claims (iss, sub, acr, amr, auth_time, exp). |
HeaderUserMiddleware | Development middleware that creates a ClaimsPrincipal from HTTP headers (X-User-Id, X-User-Name, X-User-Roles, X-User-Permissions, X-User-Tenant, X-User-Groups). |
NoOpAuthenticationHandler | Development auth handler that trusts identities set by earlier middleware. Returns NoResult when no identity is present. |
PragmaticPermissionRequirement | IAuthorizationRequirement that carries a list of required permissions and a mode (All or Any). |
PragmaticPermissionHandler | AuthorizationHandler that delegates permission checks to IPermissionChecker. |
PermissionMode | Enum: All (AND logic), Any (OR logic). |
DI Registration
Section titled “DI Registration”// Registers ICurrentUser, IPermissionChecker, IAuthorizationHandlerservices.AddPragmaticIdentity();
// With custom claim mappingservices.AddPragmaticIdentity(opts =>{ opts.UserIdClaimType = "sub"; opts.PermissionClaimType = "permissions";});IPragmaticBuilder Extensions
Section titled “IPragmaticBuilder Extensions”// Custom auth handlerapp.UseAuthentication<NoOpAuthenticationHandler>("PragmaticDefault");
// Full control via AuthenticationBuilderapp.UseAuthentication(auth => auth.AddJwtBearer(...));Both extensions call AddAuthorization() and AddPragmaticAuthorization() automatically.
When to Reference
Section titled “When to Reference”- Any Pragmatic.Design web application (this is the standard web integration).
- You want development-time header-based authentication.
- You need the permission authorization handler bridge.
3. Pragmatic.Identity.Local
Section titled “3. Pragmatic.Identity.Local”NuGet: Pragmatic.Identity.Local
Dependencies: Pragmatic.Identity, Pragmatic.Actions, Pragmatic.SourceGenerator (analyzer)
ASP.NET Core: No
EF Core: No
Self-hosted identity provider. Manages local credentials (email + password) with domain actions for registration, login, and password management. The source generator exposes these actions as HTTP endpoints when the host references this package.
Domain Actions
Section titled “Domain Actions”| Action | Type | Input | Output | Errors |
|---|---|---|---|---|
RegisterUser | DomainAction<string> | Email, Password | ExternalIdentityKey | EmailAlreadyExistsError, PasswordPolicyError |
LoginUser | DomainAction<LoginResult> | Email, Password | LoginResult (key + timestamp) | InvalidCredentialsError, AccountLockedError, IdentityNotActiveError |
ChangePassword | VoidDomainAction | CurrentPassword, NewPassword | void | InvalidCredentialsError, IdentityNotActiveError, PasswordPolicyError |
RequestPasswordReset | DomainAction<string> | Email | Reset token (or empty) | (none — never reveals email existence) |
ConfirmPasswordReset | VoidDomainAction | Email, Token, NewPassword | void | InvalidResetTokenError, PasswordPolicyError |
Service Interfaces
Section titled “Service Interfaces”| Interface | Default Implementation | Description |
|---|---|---|
ILocalIdentityStore | None (must implement) | CRUD for LocalIdentity records. Your persistence layer provides this. |
IPasswordHasher | BcryptPasswordHasher | BCrypt with configurable work factor (default: 12). |
ISecurityTokenService | HmacSecurityTokenService | SHA-256 based token generation and verification. |
IPasswordPolicy | DefaultPasswordPolicy | Minimum length validation (default: 8 chars). Replace for complexity/history/breach checks. |
LocalIdentity Entity
Section titled “LocalIdentity Entity”Extends IdentityRecord with password-specific fields:
| Property | Type | Description |
|---|---|---|
PasswordHash | string | BCrypt hash of the password |
PasswordSalt | string? | External salt (if algorithm requires it) |
Email | string? | Email address for login and reset |
EmailVerified | bool | Whether email has been verified |
ResetToken | string? | Hashed reset token |
ResetTokenExpiresAt | DateTimeOffset? | Token expiry time |
FailedLoginAttempts | int | Consecutive failed attempts |
LockoutEnd | DateTimeOffset? | Account lockout expiry |
Configuration
Section titled “Configuration”services.Configure<LocalIdentityOptions>(opts =>{ opts.PasswordWorkFactor = 12; // BCrypt rounds opts.MaxFailedLoginAttempts = 5; // Before lockout opts.LockoutDuration = TimeSpan.FromMinutes(15); opts.ResetTokenExpiry = TimeSpan.FromHours(1); opts.MinPasswordLength = 8; opts.RequireEmailVerification = false;});Domain Events
Section titled “Domain Events”All actions raise domain events via IDomainEventDispatcher:
| Event | Raised When |
|---|---|
UserRegistered | New user created |
UserLoggedIn | Successful authentication |
LoginFailed | Failed attempt (wrong password, inactive, unknown email) |
AccountLocked | Failed attempts reached threshold |
PasswordChanged | Password updated |
PasswordResetCompleted | Reset token consumed and password changed |
Security Features
Section titled “Security Features”- BCrypt hashing: Adaptive work factor, resistant to GPU attacks
- Account lockout: Configurable threshold and duration
- Reset token hashing: Tokens stored as SHA-256 hashes, not plaintext
- Timing-safe comparison:
CryptographicOperations.FixedTimeEqualsfor token verification - Email existence hiding:
RequestPasswordResetalways returns success
Package Definition
Section titled “Package Definition”LocalIdentityPackage implements IPackageDefinition:
- Route prefix:
identity/local - Required types:
DomainActionAttribute,IDomainEventDispatcher
When to Reference
Section titled “When to Reference”- You want self-hosted credential management (no external IdP dependency).
- You need registration/login as domain actions in your Pragmatic pipeline.
- You are building a standalone app or microservice with local accounts.
4. Pragmatic.Identity.Local.Jwt
Section titled “4. Pragmatic.Identity.Local.Jwt”NuGet: Pragmatic.Identity.Local.Jwt
Dependencies: Pragmatic.Identity.Local, Pragmatic.Identity.AspNetCore, Microsoft.AspNetCore.Authentication.JwtBearer
ASP.NET Core: Yes
EF Core: No
JWT token generation and validation for local identity. This is the “top” package — referencing it brings in the entire Identity.Local stack with JWT support.
Key Types
Section titled “Key Types”| Type | Description |
|---|---|
JwtTokenGenerator | Creates JWT tokens with configurable claims. Accepts subject, display name, tenant, roles, and permissions. |
JwtOptions | Configuration: SigningKey (required, 256+ bits), Issuer, Audience, TokenExpiration (default 1h), ClockSkew (default 1min). |
JwtLoginResult | Record with Token (string) and ExpiresAt (DateTimeOffset). |
Token Claims
Section titled “Token Claims”The JwtTokenGenerator.Generate() method embeds these claims in the JWT:
| Claim | Source | Always Present |
|---|---|---|
sub | ExternalIdentityKey | Yes |
jti | Random GUID | Yes |
iat | Current timestamp (epoch) | Yes |
name | displayName parameter | If provided |
tenant_id | tenantId parameter | If provided |
role | roles parameter (multi-valued) | If provided |
permission | permissions parameter (multi-valued) | If provided |
IPragmaticBuilder Extension
Section titled “IPragmaticBuilder Extension”app.UseJwtAuthentication(jwt =>{ jwt.SigningKey = "your-256-bit-secret-key"; // Required, min 32 chars jwt.Issuer = "https://myapp.example.com"; // Optional, enables issuer validation jwt.Audience = "https://myapp.example.com"; // Optional, enables audience validation jwt.TokenExpiration = TimeSpan.FromHours(1); // Default: 1 hour jwt.ClockSkew = TimeSpan.FromMinutes(1); // Default: 1 minute});This single call:
- Configures
JwtOptionsand registersJwtTokenGeneratoras singleton - Sets up
JwtBearerDefaults.AuthenticationSchemeas default scheme - Configures token validation parameters (signing key, issuer, audience, lifetime)
- Calls
AddAuthorization()andAddPragmaticAuthorization()
When to Reference
Section titled “When to Reference”- You want a complete local authentication stack with JWT tokens.
- You need both token generation (login) and token validation (API requests).
- You are building an API that issues its own tokens.
5. Pragmatic.Identity.Persistence
Section titled “5. Pragmatic.Identity.Persistence”NuGet: Pragmatic.Identity.Persistence
Dependencies: Pragmatic.Abstractions, Pragmatic.Authorization, Pragmatic.Temporal, Microsoft.EntityFrameworkCore
ASP.NET Core: No
EF Core: Yes
Database-backed authorization stores with temporal validity and Just-In-Time user provisioning. See Persistence for full details.
Key Types
Section titled “Key Types”| Type | Description |
|---|---|
EfRolePermissionStore | ITemporalRolePermissionStore backed by RolePermission table. Filters by ValidFrom/ValidTo using IClock. |
EfGroupRoleStore | ITemporalGroupRoleStore backed by GroupRole table. Same temporal filtering. |
JitProvisioningService<TUser, TKey> | Creates or updates local users on first external login. Respects SCIM ownership. |
IdentityDbContext<TUser, TKey> | Pre-configured DbContext with all identity entity mappings (deprecated — use composition). |
IdentityPersistenceBuilder | Fluent builder: EnableJitProvisioning(), SkipRolePermissionStore(), SkipGroupRoleStore(). |
Entities (all temporal with ValidFrom/ValidTo)
Section titled “Entities (all temporal with ValidFrom/ValidTo)”| Entity | Description |
|---|---|
IdentityUserBase<TKey> | Base class for user entities (deprecated — use [PragmaticUser] instead) |
ExternalIdentityRecord<TKey> | Links a user to an external IdP (issuer + subject) |
UserRole<TKey> | User-to-role assignment with temporal validity |
UserGroup<TKey> | User-to-group assignment with temporal validity |
RolePermission | Role-to-permission mapping with temporal validity |
GroupRole | Group-to-role mapping with temporal validity |
When to Reference
Section titled “When to Reference”- You need runtime-manageable role/permission assignments (not just compile-time definitions).
- You want temporal authorization (permissions that expire or activate at specific times).
- You need JIT provisioning for external identity providers.
- You are building an admin panel for role/permission management.
Package Selection Guide
Section titled “Package Selection Guide”| Scenario | Packages Needed |
|---|---|
Library that reads ICurrentUser | Pragmatic.Abstractions only |
| ASP.NET app with external IdP | Identity.AspNetCore |
| ASP.NET app with dev headers | Identity.AspNetCore |
| Self-hosted auth with JWT | Identity.Local.Jwt (pulls in everything) |
| Database-backed role management | Identity.Persistence + one of the above |
| Background job context | Identity (for SystemUser) |
| Integration tests with fake users | Identity.AspNetCore (for HeaderUserMiddleware) |