Skip to content

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 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 JitProvisioning

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.

TypeDescription
SystemUserSingleton ICurrentUser for background jobs and system operations. Always authenticated with FullAccessUserAuthorization.
IdentityOptionsConfigures which claim types map to user ID, display name, role, permission, and tenant.
IdentityRecordAbstract base class for identity provider records (local, Keycloak, etc.). Contains ExternalIdentityKey, ProvisionSource, IsActive, LastLoginAt, plus IAuditable fields.
ClaimsPermissionCheckerWraps ICurrentUser.Authorization as async IPermissionChecker for consumers that require the async interface.
ProvisionSourceEnum: Manual, Jit, Scim.
  • You are building a library that depends on ICurrentUser but not ASP.NET Core.
  • You need SystemUser for background job contexts.
  • You are implementing a custom identity provider that extends IdentityRecord.

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.

TypeDescription
ClaimsPrincipalUserAccessorScoped 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).
ClaimsAuthenticationContextReads IAuthenticationContext from claims (iss, sub, acr, amr, auth_time, exp).
HeaderUserMiddlewareDevelopment 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).
NoOpAuthenticationHandlerDevelopment auth handler that trusts identities set by earlier middleware. Returns NoResult when no identity is present.
PragmaticPermissionRequirementIAuthorizationRequirement that carries a list of required permissions and a mode (All or Any).
PragmaticPermissionHandlerAuthorizationHandler that delegates permission checks to IPermissionChecker.
PermissionModeEnum: All (AND logic), Any (OR logic).
// Registers ICurrentUser, IPermissionChecker, IAuthorizationHandler
services.AddPragmaticIdentity();
// With custom claim mapping
services.AddPragmaticIdentity(opts =>
{
opts.UserIdClaimType = "sub";
opts.PermissionClaimType = "permissions";
});
// Custom auth handler
app.UseAuthentication<NoOpAuthenticationHandler>("PragmaticDefault");
// Full control via AuthenticationBuilder
app.UseAuthentication(auth => auth.AddJwtBearer(...));

Both extensions call AddAuthorization() and AddPragmaticAuthorization() automatically.

  • 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.

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.

ActionTypeInputOutputErrors
RegisterUserDomainAction<string>Email, PasswordExternalIdentityKeyEmailAlreadyExistsError, PasswordPolicyError
LoginUserDomainAction<LoginResult>Email, PasswordLoginResult (key + timestamp)InvalidCredentialsError, AccountLockedError, IdentityNotActiveError
ChangePasswordVoidDomainActionCurrentPassword, NewPasswordvoidInvalidCredentialsError, IdentityNotActiveError, PasswordPolicyError
RequestPasswordResetDomainAction<string>EmailReset token (or empty)(none — never reveals email existence)
ConfirmPasswordResetVoidDomainActionEmail, Token, NewPasswordvoidInvalidResetTokenError, PasswordPolicyError
InterfaceDefault ImplementationDescription
ILocalIdentityStoreNone (must implement)CRUD for LocalIdentity records. Your persistence layer provides this.
IPasswordHasherBcryptPasswordHasherBCrypt with configurable work factor (default: 12).
ISecurityTokenServiceHmacSecurityTokenServiceSHA-256 based token generation and verification.
IPasswordPolicyDefaultPasswordPolicyMinimum length validation (default: 8 chars). Replace for complexity/history/breach checks.

Extends IdentityRecord with password-specific fields:

PropertyTypeDescription
PasswordHashstringBCrypt hash of the password
PasswordSaltstring?External salt (if algorithm requires it)
Emailstring?Email address for login and reset
EmailVerifiedboolWhether email has been verified
ResetTokenstring?Hashed reset token
ResetTokenExpiresAtDateTimeOffset?Token expiry time
FailedLoginAttemptsintConsecutive failed attempts
LockoutEndDateTimeOffset?Account lockout expiry
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;
});

All actions raise domain events via IDomainEventDispatcher:

EventRaised When
UserRegisteredNew user created
UserLoggedInSuccessful authentication
LoginFailedFailed attempt (wrong password, inactive, unknown email)
AccountLockedFailed attempts reached threshold
PasswordChangedPassword updated
PasswordResetCompletedReset token consumed and password changed
  • 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.FixedTimeEquals for token verification
  • Email existence hiding: RequestPasswordReset always returns success

LocalIdentityPackage implements IPackageDefinition:

  • Route prefix: identity/local
  • Required types: DomainActionAttribute, IDomainEventDispatcher
  • 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.

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.

TypeDescription
JwtTokenGeneratorCreates JWT tokens with configurable claims. Accepts subject, display name, tenant, roles, and permissions.
JwtOptionsConfiguration: SigningKey (required, 256+ bits), Issuer, Audience, TokenExpiration (default 1h), ClockSkew (default 1min).
JwtLoginResultRecord with Token (string) and ExpiresAt (DateTimeOffset).

The JwtTokenGenerator.Generate() method embeds these claims in the JWT:

ClaimSourceAlways Present
subExternalIdentityKeyYes
jtiRandom GUIDYes
iatCurrent timestamp (epoch)Yes
namedisplayName parameterIf provided
tenant_idtenantId parameterIf provided
roleroles parameter (multi-valued)If provided
permissionpermissions parameter (multi-valued)If provided
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:

  1. Configures JwtOptions and registers JwtTokenGenerator as singleton
  2. Sets up JwtBearerDefaults.AuthenticationScheme as default scheme
  3. Configures token validation parameters (signing key, issuer, audience, lifetime)
  4. Calls AddAuthorization() and AddPragmaticAuthorization()
  • 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.

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.

TypeDescription
EfRolePermissionStoreITemporalRolePermissionStore backed by RolePermission table. Filters by ValidFrom/ValidTo using IClock.
EfGroupRoleStoreITemporalGroupRoleStore 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).
IdentityPersistenceBuilderFluent builder: EnableJitProvisioning(), SkipRolePermissionStore(), SkipGroupRoleStore().

Entities (all temporal with ValidFrom/ValidTo)

Section titled “Entities (all temporal with ValidFrom/ValidTo)”
EntityDescription
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
RolePermissionRole-to-permission mapping with temporal validity
GroupRoleGroup-to-role mapping with temporal validity
  • 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.
ScenarioPackages Needed
Library that reads ICurrentUserPragmatic.Abstractions only
ASP.NET app with external IdPIdentity.AspNetCore
ASP.NET app with dev headersIdentity.AspNetCore
Self-hosted auth with JWTIdentity.Local.Jwt (pulls in everything)
Database-backed role managementIdentity.Persistence + one of the above
Background job contextIdentity (for SystemUser)
Integration tests with fake usersIdentity.AspNetCore (for HeaderUserMiddleware)