Pragmatic.Identity
Identity and authentication stack for the Pragmatic.Design ecosystem. Five packages that layer from pure abstractions up to full database-backed authorization 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’s identity, permissions, or tenant repeats the same pattern: resolve IHttpContextAccessor, null-check HttpContext, navigate claims with magic strings, handle provider-specific claim type URIs. The claim parsing is scattered, stringly typed, and breaks when you switch identity providers. Background jobs have no HttpContext at all, so the entire pattern fails outside HTTP requests.
// This is what every service looks like without Pragmatic.Identityvar userId = principal?.FindFirst("sub")?.Value ?? principal?.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value;var roles = principal?.FindAll("http://schemas.microsoft.com/ws/2008/06/identity/claims/role") .Select(c => c.Value);The problems compound:
- Magic strings everywhere. Every service hardcodes claim type URIs. Change your identity provider and you hunt down every occurrence.
- No background job support.
IHttpContextAccessorreturns null in hosted services, event handlers, and seed scripts. You need a separate abstraction that never existed. - Authorization is scattered. Permission checks are inline
ifstatements. There is no unified way to resolve roles into permissions, cache the result, or apply wildcard matching. - Testing requires HTTP infrastructure. To test code that reads
ClaimsPrincipal, you need to fabricate anHttpContext. Domain logic should not need HTTP plumbing.
The Solution
Section titled “The Solution”Pragmatic.Identity replaces ClaimsPrincipal with a structured ICurrentUser interface that separates identity, authorization, and authentication into dedicated sub-objects. Claim type mapping is centralized and configurable. The same code works across HTTP requests (ClaimsPrincipalUserAccessor), background jobs (SystemUser), and tests (HeaderUserMiddleware) without modification.
// Type-safe, no magic strings, works everywherepublic class OrderService(ICurrentUser currentUser){ public async Task CreateOrder(CreateOrderRequest request) { var userId = currentUser.Id; var tenantId = currentUser.TenantId; if (!currentUser.Authorization.HasPermission("orders.create")) return Result.Failure(new ForbiddenError()); }}Development and production authentication switch via configuration — the same codebase runs with HTTP headers (dev) or JWT tokens (production) based on whether a signing key is present in appsettings.json.
Package Overview
Section titled “Package Overview”| Package | Purpose | ASP.NET Core? | EF Core? |
|---|---|---|---|
| Pragmatic.Identity | Core runtime: SystemUser, IdentityOptions, ClaimsPermissionChecker | No | No |
| Pragmatic.Identity.AspNetCore | Maps ClaimsPrincipal to ICurrentUser, dev auth middleware | Yes | No |
| Pragmatic.Identity.Local | Self-hosted identity provider: register, login, password reset | No | No |
| Pragmatic.Identity.Local.Jwt | JWT token generation/validation for Local identity | Yes | No |
| Pragmatic.Identity.Persistence | EF Core stores for temporal roles, groups, JIT provisioning | No | Yes |
Dependency Graph
Section titled “Dependency Graph”Pragmatic.Abstractions (ICurrentUser, IUserAuthorization, PrincipalKind, AnonymousUser) |Pragmatic.Identity (SystemUser, IdentityOptions, ClaimsPermissionChecker, IdentityRecord) | +----+----+ | | v vIdentity. Identity.Local (LocalIdentity, RegisterUser, LoginUser, ChangePassword,AspNetCore RequestPasswordReset, ConfirmPasswordReset, BcryptPasswordHasher) | | +----+----+ | vIdentity.Local.Jwt (JwtTokenGenerator, JwtOptions, UseJwtAuthentication)
Pragmatic.Authorization (CachedPermissionResolver, IPermissionProvider, IRolePermissionStore) | vIdentity.Persistence (EfRolePermissionStore, EfGroupRoleStore, JitProvisioningService, IdentityUserBase, UserRole, UserGroup, RolePermission, GroupRole)Quick Start
Section titled “Quick Start”Development (header-based auth, no IdP required)
Section titled “Development (header-based auth, no IdP required)”await PragmaticApp.RunAsync(args, app =>{ app.UseAuthentication<NoOpAuthenticationHandler>("PragmaticDefault");});Requests authenticate via HTTP headers:
GET /api/reservationsX-User-Id: user-42X-User-Name: Jane DoeX-User-Roles: admin,booking-managerX-User-Permissions: reservation.create,reservation.readX-User-Tenant: tenant-1X-User-Groups: customer-careProduction (JWT authentication)
Section titled “Production (JWT authentication)”await PragmaticApp.RunAsync(args, app =>{ app.UseJwtAuthentication(jwt => { jwt.SigningKey = app.Configuration["Jwt:Key"]!; jwt.Issuer = "https://myapp.example.com"; jwt.TokenExpiration = TimeSpan.FromHours(1); });});Config-driven (dev or prod from appsettings)
Section titled “Config-driven (dev or prod from appsettings)”await PragmaticApp.RunAsync(args, app =>{ var jwtKey = app.Configuration["Jwt:Key"]; if (!string.IsNullOrEmpty(jwtKey)) { app.UseJwtAuthentication(jwt => { jwt.SigningKey = jwtKey; jwt.Issuer = app.Configuration["Jwt:Issuer"]; }); } else { app.UseAuthentication<NoOpAuthenticationHandler>("PragmaticDefault"); }});ICurrentUser API
Section titled “ICurrentUser API”The ICurrentUser interface is the central identity abstraction. It is defined in Pragmatic.Abstractions and implemented by ClaimsPrincipalUserAccessor at runtime.
public interface ICurrentUser{ string Id { get; } // User identifier (empty for anonymous) string? DisplayName { get; } // Human-readable name bool IsAuthenticated { get; } // Whether authenticated PrincipalKind Kind { get; } // Anonymous | User | Service | System string? TenantId { get; } // Multi-tenant identifier string? ImpersonatedBy { get; } // Impersonator user ID (if applicable)
IReadOnlyDictionary<string, IReadOnlyList<string>> Claims { get; } // All claims, multi-valued
IUserAuthorization Authorization { get; } // Roles, permissions, groups, scopes IAuthenticationContext Authentication { get; } // Scheme, issuer, MFA, expiration}PrincipalKind
Section titled “PrincipalKind”| Value | Description | Example |
|---|---|---|
Anonymous | No authenticated identity | Unauthenticated requests |
User | Human user via identity provider | Browser session, mobile app |
Service | Machine-to-machine caller | API key, client credentials |
System | The application itself | Background jobs, data migrations |
Built-in Implementations
Section titled “Built-in Implementations”| Type | IsAuthenticated | Kind | Authorization | Use Case |
|---|---|---|---|---|
AnonymousUser | false | Anonymous | NullUserAuthorization (all denied) | Fallback for unauthenticated requests |
SystemUser | true | System | FullAccessUserAuthorization (all granted) | Background jobs, seed, migrations |
ClaimsPrincipalUserAccessor | from claims | User | CachedPermissionResolver (lazy) | HTTP requests |
CurrentUserExtensions
Section titled “CurrentUserExtensions”Extension methods for common operations on ICurrentUser:
// Safely get nullable Id (null instead of empty for anonymous)string? userId = currentUser.IdOrNull();
// Display-friendly name with fallbackstring displayName = currentUser.DisplayNameOrId(); // "Jane Doe" or "user-42"
// Typed claim accessstring? email = currentUser.GetClaim("email");IReadOnlyList<string> scopes = currentUser.GetClaims("scope");IUserAuthorization
Section titled “IUserAuthorization”public interface IUserAuthorization{ IReadOnlyCollection<string> Roles { get; } IReadOnlySet<string> Permissions { get; } // Expanded via permission providers IReadOnlyCollection<string> Groups { get; } IReadOnlyCollection<string> Scopes { get; }
bool HasPermission(string permission); // Exact + wildcard match bool HasAnyPermission(IEnumerable<string> permissions); // OR bool HasAllPermissions(IEnumerable<string> permissions); // AND bool IsInRole(string role); bool IsInGroup(string group); bool HasScope(string scope);}IAuthenticationContext
Section titled “IAuthenticationContext”public interface IAuthenticationContext{ string? Scheme { get; } // "Bearer", "Cookie" string? Protocol { get; } // "oidc", "saml2", "apikey" string? Issuer { get; } // Token issuer URI string? Subject { get; } // Subject identifier bool IsMfaAuthenticated { get; } // Multi-factor status DateTimeOffset? AuthenticatedAt { get; } DateTimeOffset? ExpiresAt { get; } string? ExternalIdentityKey { get; } // "{issuer}|{subject}" for IdP correlation}Permission Resolution Chain
Section titled “Permission Resolution Chain”The authorization pipeline resolves permissions through a provider chain:
ClaimsPrincipal (HTTP request) | vClaimsPrincipalUserAccessor (ICurrentUser) | vCachedPermissionResolver (IUserAuthorization) | Collects from all IPermissionProvider instances, ordered by .Order | +-- ClaimsPermissionProvider (Order=0, reads "permission" claims) +-- RoleExpansionProvider (Order=100, role -> permissions via IRolePermissionStore) +-- GroupExpansionProvider (Order=200, group -> roles -> permissions via IGroupRoleStore) +-- [Custom providers...] | vMerged permission set (HashSet<string>, case-insensitive) | vWildcardMatcher for pattern checks (e.g., "booking.*" matches "booking.reservation.create")Permission resolution is lazy (first access triggers resolution) and cached per request (or cross-request via ICacheStack using CacheCategories.Permissions when configured).
Wildcard Matching
Section titled “Wildcard Matching”WildcardMatcher.Matches() supports glob-style patterns:
// Exact matchcurrentUser.Authorization.HasPermission("booking.reservation.create"); // true
// Wildcard permission in the user's set// If user has "booking.*", then:currentUser.Authorization.HasPermission("booking.reservation.create"); // truecurrentUser.Authorization.HasPermission("billing.invoice.read"); // falseClaim Mapping Configuration
Section titled “Claim Mapping Configuration”IdentityOptions controls which claim types are used to populate ICurrentUser properties. Override defaults when your identity provider uses non-standard claim types.
services.Configure<IdentityOptions>(options =>{ // Standard claim types (defaults) options.UserIdClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"; options.DisplayNameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"; options.RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"; options.PermissionClaimType = "permission"; options.TenantClaimType = "tenant_id";});| Property | Default | Description |
|---|---|---|
UserIdClaimType | http://schemas.xmlsoap.org/.../nameidentifier | Claim type for user ID |
DisplayNameClaimType | http://schemas.xmlsoap.org/.../name | Claim type for display name |
RoleClaimType | http://schemas.microsoft.com/.../role | Claim type for roles |
PermissionClaimType | permission | Claim type for permissions |
TenantClaimType | tenant_id | Claim type for tenant identifier |
Common overrides for popular providers:
// Auth0options.UserIdClaimType = "sub";options.PermissionClaimType = "permissions";
// Azure AD / Entra IDoptions.RoleClaimType = "roles";options.TenantClaimType = "tid";
// Keycloakoptions.UserIdClaimType = "sub";options.RoleClaimType = "realm_access.roles";Feature Catalog
Section titled “Feature Catalog”Pragmatic.Identity (Core)
Section titled “Pragmatic.Identity (Core)”SystemUser— singleton for background jobs with full accessIdentityOptions— configurable claim type mapping (user ID, name, role, permission, tenant)IdentityRecord— abstract base for provider-specific identity dataClaimsPermissionChecker— bridgesICurrentUser.Authorizationto asyncIPermissionCheckerProvisionSource— enum (Local,External,Jit) tracking how a user was provisioned
Pragmatic.Identity.AspNetCore
Section titled “Pragmatic.Identity.AspNetCore”ClaimsPrincipalUserAccessor— resolvesICurrentUserfromHttpContext.UserClaimsAuthenticationContext— mapsClaimsPrincipalauthentication properties toIAuthenticationContextHeaderUserMiddleware— dev middleware that createsClaimsPrincipalfrom HTTP headersNoOpAuthenticationHandler— dev auth handler that trusts pre-set identitiesPragmaticPermissionRequirement— ASP.NET Core authorization requirementPragmaticPermissionHandler— bridges permission checks toIPermissionCheckerPermissionMode— enum controlling permission check behavior (All, Any)AddPragmaticIdentity()— registersICurrentUser,IPermissionChecker, authorization handlerUseAuthentication()/UseAuthentication<THandler>()—IPragmaticBuilderextensions
Pragmatic.Identity.Local
Section titled “Pragmatic.Identity.Local”Self-hosted identity actions that you deploy inside your own host — no external IdP dependency.
- Actions:
RegisterUser,LoginUser,ChangePassword,RequestPasswordReset,ConfirmPasswordReset - Services:
IPasswordHasher/BcryptPasswordHasher,ISecurityTokenService/HmacSecurityTokenService,IPasswordPolicy/DefaultPasswordPolicy,ILocalIdentityStore(persistence abstraction) - Events:
UserRegistered,UserLoggedIn,LoginFailed,AccountLocked,PasswordChanged,PasswordResetCompleted - Errors:
InvalidCredentialsError,AccountLockedError,EmailAlreadyExistsError,InvalidResetTokenError,IdentityNotActiveError,PasswordPolicyError - Security: BCrypt password hashing, account lockout, brute-force protection, token-based password reset
- Package:
LocalIdentityPackage(IPackageDefinition) with route prefixidentity/local - Permissions:
LocalIdentityPermissions— SG-generated constants for identity actions
Pragmatic.Identity.Local.Jwt
Section titled “Pragmatic.Identity.Local.Jwt”JwtTokenGenerator— creates JWT tokens with configurable claims (sub, name, tenant, roles, permissions)JwtOptions— signing key, issuer, audience, expiration, clock skewJwtLoginResult— token + expiration recordUseJwtAuthentication()—IPragmaticBuilderextension for full JWT setup (generation + validation)
Pragmatic.Identity.Persistence
Section titled “Pragmatic.Identity.Persistence”- Entities:
IdentityUserBase<TKey>,ExternalIdentityRecord<TKey>,UserRole<TKey>,UserGroup<TKey>,RolePermission,GroupRole— all temporal (ValidFrom/ValidTo) - EF Stores:
EfRolePermissionStore(temporal role-to-permission),EfGroupRoleStore(temporal group-to-role) - JIT Provisioning:
JitProvisioningService<TUser, TKey>— create/update users on first external login - DbContext:
IdentityDbContext<TUser, TKey>with pre-configured entity mappings - Builder:
IdentityPersistenceBuilderwithEnableJitProvisioning(),SkipRolePermissionStore(),SkipGroupRoleStore() - Configurations:
RolePermissionConfiguration,GroupRoleConfiguration,UserRoleConfiguration,UserGroupConfiguration,ExternalIdentityRecordConfiguration
Identity.Local Actions
Section titled “Identity.Local Actions”The Pragmatic.Identity.Local package provides 5 domain actions for local password authentication:
| Action | Route | Description |
|---|---|---|
RegisterUser | POST /identity/local/register | Create new local identity with email + password |
LoginUser | POST /identity/local/login | Authenticate and return JWT token |
ChangePassword | POST /identity/local/change-password | Update password (requires authentication) |
RequestPasswordReset | POST /identity/local/request-reset | Generate reset token (sent via email) |
ConfirmPasswordReset | POST /identity/local/confirm-reset | Complete reset with token + new password |
LocalIdentityOptions
Section titled “LocalIdentityOptions”Configure the local identity provider behavior:
services.Configure<LocalIdentityOptions>(options =>{ options.MinPasswordLength = 10; // default: 8 options.MaxFailedLoginAttempts = 3; // default: 5 options.LockoutDuration = TimeSpan.FromMinutes(30); // default: 15 min options.ResetTokenExpiry = TimeSpan.FromHours(2); // default: 1 hour options.PasswordWorkFactor = 12; // BCrypt cost factor, default: 12 options.RequireEmailVerification = true; // default: false});| Property | Default | Description |
|---|---|---|
MinPasswordLength | 8 | Minimum password length |
MaxFailedLoginAttempts | 5 | Failed attempts before lockout |
LockoutDuration | 15 min | Duration of account lockout |
ResetTokenExpiry | 1 hour | Password reset token validity |
PasswordWorkFactor | 12 | BCrypt work factor (higher = slower + more secure) |
RequireEmailVerification | false | Block login until email is verified |
Password Policy
Section titled “Password Policy”The IPasswordPolicy interface allows custom password rules. The built-in DefaultPasswordPolicy checks MinPasswordLength:
// Register a custom policyservices.AddSingleton<IPasswordPolicy, StrongPasswordPolicy>();
public class StrongPasswordPolicy : IPasswordPolicy{ public VoidResult<PasswordPolicyError> Validate(string password) { if (password.Length < 12) return new PasswordPolicyError("Password must be at least 12 characters."); if (!password.Any(char.IsUpper)) return new PasswordPolicyError("Password must contain an uppercase letter."); if (!password.Any(char.IsDigit)) return new PasswordPolicyError("Password must contain a digit."); return VoidResult<PasswordPolicyError>.Success(); }}Domain Events
Section titled “Domain Events”Each identity action raises domain events that you can handle for audit logging, notifications, or analytics:
[DomainAction]public class SendWelcomeEmail : IDomainEventHandler<UserRegistered>{ public async Task HandleAsync(UserRegistered @event, CancellationToken ct) { // Send welcome email to @event.Email }}
[DomainAction]public class AuditLoginFailure : IDomainEventHandler<LoginFailed>{ public async Task HandleAsync(LoginFailed @event, CancellationToken ct) { // Log failed attempt for @event.Email from @event.IpAddress }}Temporal Authorization (Persistence)
Section titled “Temporal Authorization (Persistence)”All role and permission assignments in Identity.Persistence are temporal — they have ValidFrom and ValidTo dates. This enables:
- Scheduled role grants: “This contractor has admin access from March 1 to March 31.”
- Automatic expiration: No cleanup jobs needed; the query filter handles it.
- Audit trail: Past assignments remain in the database for compliance.
// Role assigned from today until end of Marchvar userRole = new UserRole<Guid>{ UserId = userId, RoleName = "contractor-admin", ValidFrom = DateTimeOffset.UtcNow, ValidTo = new DateTimeOffset(2026, 3, 31, 23, 59, 59, TimeSpan.Zero)};The EfRolePermissionStore and EfGroupRoleStore filter assignments by IClock.UtcNow, so expired grants are invisible to the authorization pipeline.
JIT Provisioning
Section titled “JIT Provisioning”JitProvisioningService<TUser, TKey> creates or updates user entities on first external login. When a user authenticates via an external IdP (Google, Azure AD, Okta), JIT provisioning:
- Checks if an
ExternalIdentityRecordexists for the{issuer}|{subject}pair - If not found, creates a new
TUserentity and links the external identity - If found, updates claims and profile data from the latest token
- Supports SCIM-aware attribute mapping
// In Program.csapp.UseIdentityPersistence<ApplicationUser, Guid>(builder =>{ builder.EnableJitProvisioning();});Integration with Pragmatic.Authorization
Section titled “Integration with Pragmatic.Authorization”The Authorization package defines the role/permission infrastructure that Identity consumes:
IPragmaticBuilder.UseAuthorization()— configures roles, groups, resource authorizers, policiesMapRole<T>()— maps anIRoleto its permissions (static or composed fromIRoleDefinition)MapGroup<T>()— maps anIGroupto its rolesDefaultEndpointPolicy— default policy for all endpoints (RequireAuthenticated,AllowAnonymous)ResourceAuthorizationFilter— instance-level ABAC checks viaIResourceAuthorizer<T>(located inPragmatic.Actions.Pipeline)SeedFromJson()— loads additional role definitions fromauthorization.json
await PragmaticApp.RunAsync(args, app =>{ app.UseAuthorization(auth => { auth.MapRole<AdminRole>(r => r .IncludeDefinition<BookingPermissions>() .IncludeDefinition<BillingPermissions>());
auth.MapGroup<CustomerCareGroup>(g => g .WithRole<SupportRole>() .WithRole<ViewerRole>() .WithDataScopes("region:eu", "region:us"));
auth.AddResourceAuthorizer<InvoiceAuthorizer>(); });});Header Authentication (Development)
Section titled “Header Authentication (Development)”The HeaderUserMiddleware reads HTTP headers to construct a ClaimsPrincipal for development environments. No external IdP or JWT signing key is needed.
| Header | Maps To | Separator | Example |
|---|---|---|---|
X-User-Id | ICurrentUser.Id | — | user-42 |
X-User-Name | ICurrentUser.DisplayName | — | Jane Doe |
X-User-Roles | IUserAuthorization.Roles | , | admin,manager |
X-User-Permissions | IUserAuthorization.Permissions | , | booking.create,billing.read |
X-User-Tenant | ICurrentUser.TenantId | — | tenant-1 |
X-User-Groups | IUserAuthorization.Groups | , | customer-care |
All headers are optional. If X-User-Id is absent, the request is treated as anonymous.
Testing
Section titled “Testing”Unit Tests — Mock ICurrentUser
Section titled “Unit Tests — Mock ICurrentUser”[Fact]public async Task CreateOrder_WithPermission_Succeeds(){ var user = Substitute.For<ICurrentUser>(); user.Id.Returns("user-42"); user.Authorization.HasPermission("orders.create").Returns(true);
var service = new OrderService(user); var result = await service.CreateOrder(new CreateOrderRequest { /* ... */ });
result.IsSuccess.Should().BeTrue();}Integration Tests — SystemUser
Section titled “Integration Tests — SystemUser”// Background job context -- full access, no HTTPservices.AddScoped<ICurrentUser>(_ => SystemUser.Instance);Integration Tests — TenantScope + Identity
Section titled “Integration Tests — TenantScope + Identity”[Fact]public async Task SeedData_WithSystemUser_BypassesAuthorization(){ using var tenant = TenantScope.BeginScope("test-tenant"); // SystemUser.Instance has FullAccessUserAuthorization // All permission checks return true}Samples
Section titled “Samples”See samples/Pragmatic.Identity.Samples/ for 3 runnable scenarios: ICurrentUser (AnonymousUser/SystemUser singletons, PrincipalKind, property composition), integration patterns (DI injection, claim mapping, ASP.NET Core bridge), and claims/extensions (runtime user inspection, SystemUser full access).
Feature Summary
Section titled “Feature Summary”| Problem | Solution |
|---|---|
| Magic strings for claim types | IdentityOptions centralized claim mapping |
| No identity in background jobs | SystemUser.Instance singleton with full access |
| Scattered permission checks | IUserAuthorization.HasPermission() with wildcard matching |
| Testing requires HTTP plumbing | AnonymousUser / SystemUser singletons, mockable ICurrentUser |
| Dev environment needs IdP | HeaderUserMiddleware + NoOpAuthenticationHandler |
| Production needs JWT | UseJwtAuthentication() one-liner |
| Role-to-permission mapping | Provider chain: claims, roles, groups, custom |
| Temporal role assignments | ValidFrom/ValidTo on all grant entities |
| External IdP user provisioning | JitProvisioningService — auto-create on first login |
| Password management | Identity.Local with BCrypt, lockout, reset tokens |
| Claim type varies by IdP | IdentityOptions — configure once, override per provider |
| MFA detection | IAuthenticationContext.IsMfaAuthenticated |
| Impersonation tracking | ICurrentUser.ImpersonatedBy |
Additional Documentation
Section titled “Additional Documentation”Detailed documentation is available in the docs/ directory:
- Architecture and Core Concepts — why Pragmatic.Identity exists, how its pieces fit together, ICurrentUser composition
- Getting Started — step-by-step setup from zero to working auth
- Package Architecture — detailed description of each sub-package
- JWT Authentication — JWT setup, token claims, configuration
- Persistence — EF stores, temporal authorization, JIT provisioning
- Dev Authentication — HeaderUserMiddleware, header format, testing
- Common Mistakes — Wrong/Right/Why format for common pitfalls
- Troubleshooting — problem/solution guide with diagnostics reference
Cross-Module Integration
Section titled “Cross-Module Integration”| With Module | Integration |
|---|---|
| Pragmatic.Authorization | Permission resolution, caching, policies, resource authorizers |
| Pragmatic.Actions | [RequirePermission], [RequirePolicy<T>] on actions and mutations |
| Pragmatic.MultiTenancy | ICurrentUser.TenantId feeds tenant context |
| Pragmatic.Persistence.EFCore | IAuditable.CreatedBy/UpdatedBy populated from ICurrentUser.Id |
| Pragmatic.FeatureFlags | ICurrentUser.Id feeds FeatureFlagContext.UserId |
| Pragmatic.Composition | IPragmaticBuilder.UseAuthentication(), UseJwtAuthentication() |
Requirements
Section titled “Requirements”- .NET 10.0+
Pragmatic.Abstractions(interfaces)Pragmatic.Authorization(permission infrastructure)
License
Section titled “License”Part of the Pragmatic.Design ecosystem.