Skip to content

Current User

ICurrentUser is the enriched per-request user context — it includes resolved roles and flattened permissions from the RBAC system. Built by IdentityMiddleware on every request, it's the bridge between authentication (JWT claims) and authorization (permission checks).

ICurrentUser

public interface ICurrentUser
{
    string UserId { get; }
    string Email { get; }
    string Name { get; }
    IReadOnlySet<string> Roles { get; }
    IReadOnlySet<int> Permissions { get; }
    bool HasPermission<TPermission>(TPermission permission) where TPermission : Enum;
    bool HasRole(string roleName);
    string? TenantId { get; }
    bool IsAuthenticated { get; }
}

Unlike IUserContext (which reads JWT claims directly), ICurrentUser includes the full RBAC state: which roles the user holds, and which permissions those roles grant.

Usage in Handlers

ICurrentUser is not effects-based — inject it directly or access it through IServiceProvider:

// Use HasPermission for programmatic checks
if (!currentUser.HasPermission(Permission.Contacts_Delete))
    return FailEff<Unit>(Error.New(403, "Insufficient permissions"));

For declarative access control on handlers, use [Authorize] and [Require] instead — see Authorization.

IdentityMiddleware

IdentityMiddleware runs after ASP.NET's UseAuthentication() / UseAuthorization() and builds ICurrentUser per-request:

  1. Reads UserId from CorrelationContext (set by prior auth middleware)
  2. If unauthenticated: registers CurrentUser.Anonymous
  3. If authenticated:
    • Calls IIdentityStore.GetRolesAsync(userId) to resolve role assignments
    • Calls IPermissionResolver.ResolvePermissions(roleNames) to flatten roles into permissions
    • Reads email/name from HttpContext.User claims
    • Constructs CurrentUser with the full RBAC state
UseAuthentication()     → sets ClaimsPrincipal from JWT
UseAuthorization()      → runs ASP.NET authorization
IdentityMiddleware      → builds ICurrentUser from IIdentityStore + IPermissionResolver

IIdentityStore

The persistence interface for user identity and role management:

public interface IIdentityStore
{
    Task<UserIdentity?> GetAsync(string userId, CancellationToken ct = default);
    Task<IReadOnlyList<RoleAssignment>> GetRolesAsync(string userId, CancellationToken ct = default);
    Task AssignRoleAsync(string userId, string roleName, CancellationToken ct = default);
    Task RevokeRoleAsync(string userId, string roleName, CancellationToken ct = default);
}

Supporting records:

public sealed record UserIdentity(
    string UserId, string Email, string Name,
    IReadOnlyList<RoleAssignment> Roles, DateTimeOffset CreatedAt);

public sealed record RoleAssignment(
    string RoleName, DateTimeOffset AssignedAt, string? AssignedBy);

The default InMemoryIdentityStore is [DevelopmentOnly]. Production implementations persist to your data store.

IPermissionResolver

Resolves a set of role names into a flattened set of permission integers:

public interface IPermissionResolver
{
    IReadOnlySet<int> ResolvePermissions(IReadOnlySet<string> roles);
}

When you define [Permissions] and [Roles], the generator creates a GeneratedPermissionResolver that maps role names to their granted permissions. Without RBAC attributes, NullPermissionResolver.Instance returns an empty set.

User Entity Conventions

When using IdentityModule<TUser>, the generator inspects your user entity's properties:

Property Type Effect
Email string Required — login identifier
PasswordHash string Enables local auth (login/register) endpoints
GoogleSub or GoogleId string Enables Google OAuth endpoints
Roles IReadOnlySet<string> Enables role management

Diagnostics

ID Severity Description Code Fix
DSID02 Info User entity has no PasswordHash — local auth endpoints disabled
DSID03 Info User entity has no Roles property — role management disabled Add Roles property
DSID07 Info User entity has neither PasswordHash nor GoogleSub/GoogleId