Skip to main content

Authentication & Authorization Packages

Enterprise-grade authentication and authorization packages providing JWT token management, rule-driven permission evaluation, and Backend-for-Frontend (BFF) patterns for secure SPA communication.

Muonroi.Auth

NuGet: Muonroi.Auth | Tier: OSS | Distribution: NuGet.org | License: MIT

Purpose

Provides JWT authentication infrastructure including token generation, validation, cryptographic signing, and password hashing. Supports RSA key rotation, token revocation, WebAuthn (FIDO2) authentication, and OIDC integration.

Key Types

TypeKindPurpose
JwtServiceClassGenerate, validate, revoke JWT tokens. Manages RSA signing credentials and token revocation.
IRsaKeyStoreInterfaceAbstraction for RSA key management (in-memory or Redis-backed).
ITokenRevocationStoreInterfaceToken blacklist/revocation management.
HmacTokenSignerClassHMAC-SHA256 token signing for symmetric key scenarios.
RsaTokenSignerClassRSA token signing with asymmetric keys.
MPasswordHelperClassBCrypt password hashing and verification utilities.
BCryptPasswordHasherClassIPasswordHasher implementation using BCrypt.NET.
IPasswordHasherInterfacePluggable password hashing contract.
InMemoryRsaKeyStoreClassIn-memory RSA key storage (development/testing).
RedisRsaKeyStoreClassRedis-backed RSA key store (production).
TokenRevocationStoreClassIn-memory token revocation store.
RedisTokenRevocationStoreClassRedis-backed token revocation store.
WebAuthnServiceClassFIDO2 WebAuthn registration and authentication.
PkceClientClassOAuth 2.0 PKCE (Proof Key for Code Exchange) client.
DPoPBindingServiceClassDPoP (Demonstration of Proof-of-Possession) token binding.
OidcHandlerClassOpenID Connect authentication handler.

Configuration

Bind JWT settings from appsettings.json:

{
"Jwt": {
"Issuer": "https://your-auth-server.com",
"Audience": "your-application",
"Expires": 3600
},
"Authentication": {
"RefreshTokenLifetimeMinutes": 43200
}
}

DI Registration

using Muonroi.Auth;

var services = new ServiceCollection();

// Option 1: In-memory RSA key store (development)
services.AddInMemoryRsaKeyStore();

// Option 2: Redis-backed RSA key store (production)
services.AddRedisRsaKeyStore(configuration);

// Option 3: Register token revocation separately
services.AddDefaultTokenRevocationStore();

// Access the JWT service
var jwtService = services.BuildServiceProvider()
.GetRequiredService<JwtService>();

Usage Example

// Generate a JWT token
var token = jwtService.GenerateToken(
subject: "user-123",
lifetime: TimeSpan.FromHours(1),
notBefore: DateTime.UtcNow);

// Validate a token
try
{
var principal = jwtService.ValidateToken(token);
var userId = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
}
catch (SecurityTokenException ex)
{
// Token is invalid or revoked
}

// Revoke a token
jwtService.RevokeToken(token);

// Rotate RSA keys
jwtService.RotateKeys();

// Get public keys (JWKS endpoint)
var jwks = jwtService.GetJsonWebKeySet();

WebAuthn Setup

services.AddWebAuthnServices();

var webAuthnService = sp.GetRequiredService<WebAuthnService>();

// Register a credential
var registerResponse = await webAuthnService.RegisterAsync(
userId: "user-123",
userName: "[email protected]",
credentialName: "MacBook Pro");

// Authenticate with a credential
var authenticateResponse = await webAuthnService.AuthenticateAsync(
userId: "user-123",
credential: clientResponse);

Muonroi.AuthZ

NuGet: Muonroi.AuthZ | Tier: Commercial | Distribution: GitHub Packages | License: Proprietary

Purpose

Rule-driven authorization engine integrating the Muonroi Rule Engine with ASP.NET Core authorization. Supports attribute-based access control (ABAC), role-based access control (RBAC), row-level security (RLS), and hot-reload of authorization rules from the Control Plane without application restart.

Key Types

TypeKindPurpose
IAuthorizationPolicyEvaluatorInterfaceEvaluates authorization rules against a context.
RuleEngineAuthorizationPolicyEvaluatorClassImplementation using Muonroi Rule Engine with caching.
AuthorizationRuleContextClassRule evaluation context carrying user, tenant, resource, action, and claims.
AuthorizationResultClassAllow/Deny decision with optional denial reason.
MuonroiAuthorizationHandlerClassASP.NET Core authorization handler that bridges claims and rules.
MuonroiAuthorizationRequirementClassASP.NET Core authorization requirement for rule-engine policies.
AuthorizationRuleOrchestratorAdapterClassAdapter wrapping RuleOrchestrator for authorization context.
IRuleRowFilter<T>InterfaceApplies rule-driven row-level filtering to queryables.
RuleRowFilter<T>ClassImplementation using Rule Engine for RLS.
RowFilterContext<T>ClassRow filter evaluation context.
OpaAuthorizationServiceClassAlternative Open Policy Agent (OPA) integration.
IAuthRuleChangeHandlerInterfaceHandles authorization rule changes from hot-reload.
AuthRuleHotReloadClientClassSignalR client for Control Plane rule updates.
DefaultAuthRuleChangeHandlerClassDefault hot-reload handler (resets cache/refreshes rules).

Authorization Context Model

public sealed class AuthorizationRuleContext : IRuleContext
{
public string UserId { get; init; } // User requesting access
public string TenantId { get; init; } // Tenant context
public string Resource { get; init; } // Resource (e.g., "orders", "reports")
public string Action { get; init; } // Action (e.g., "read", "write", "delete")
public IReadOnlyList<string> Roles { get; init; } // User roles
public IReadOnlyDictionary<string, object?> Claims { get; init; } // Additional claims
public void HaltGroup() // Signal engine to stop rule group
public bool IsHalted { get; }
}

DI Registration

using Muonroi.AuthZ.Extensions;

var services = new ServiceCollection();

// Register the rule-engine-driven authorization handler
services.AddMAuthorizationRuleEngine();

// Register your authorization rules
services.AddScoped<IRule<AuthorizationRuleContext>, CanReadOrdersRule>();
services.AddScoped<IRule<AuthorizationRuleContext>, CanDeleteInvoicesRule>();

// Optional: Enable hot-reload from Control Plane
services.AddMAuthorizationHotReload(options =>
{
options.ControlPlaneUrl = "https://control-plane.example.com";
options.TenantId = "tenant-123";
options.AccessTokenFactory = async () => await GetTokenAsync();
options.ReconnectDelay = TimeSpan.FromSeconds(10);
});

Authorization Rule Example

using Muonroi.AuthZ.Authorization;
using Muonroi.RuleEngine.Abstractions;

public class CanReadOrdersRule : IRule<AuthorizationRuleContext>
{
public string Code => "can-read-orders";
public int Order => 1;
public string[] DependsOn => [];
public HookPoint HookPoint => HookPoint.BeforeRule;
public RuleType Type => RuleType.Business;

public async Task<RuleResult> EvaluateAsync(
AuthorizationRuleContext context,
FactBag facts,
CancellationToken ct = default)
{
// Check if action is "read"
if (context.Action != "read")
{
return RuleResult.Passed();
}

// Allow if user has "Sales" role
if (context.Roles.Contains("Sales"))
{
return RuleResult.Success();
}

return RuleResult.Failure(["User lacks Sales role"]);
}

public Task ExecuteAsync(AuthorizationRuleContext context, CancellationToken ct = default)
{
return Task.CompletedTask;
}
}

Usage in Controllers

[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
private readonly IAuthorizationService _authorizationService;

[HttpGet("{id}")]
[RequireRuleEngineAuthorization("orders", "read")]
public async Task<ActionResult<OrderDto>> GetOrder(string id)
{
// Authorization is enforced by the policy
return Ok(new OrderDto { Id = id });
}

[HttpDelete("{id}")]
public async Task<IActionResult> DeleteOrder(string id)
{
// Manual authorization check
var result = await _authorizationService
.AuthorizeAsync(User, new MuonroiAuthorizationRequirement("orders", "delete"));

if (!result.Succeeded)
{
return Forbid();
}

return NoContent();
}
}

Row-Level Security (RLS)

// Define a row filter rule
public class UserCanAccessOwnInvoicesRule : IRule<RowFilterContext<Invoice>>
{
public string Code => "access-own-invoices";
public int Order => 1;

public async Task<RuleResult> EvaluateAsync(
RowFilterContext<Invoice> context,
FactBag facts,
CancellationToken ct = default)
{
// Filter invoices to only those owned by the current user
var userId = context.UserId;
context.Query = context.Query.Where(inv => inv.OwnerId == userId);

return RuleResult.Success();
}

public Task ExecuteAsync(RowFilterContext<Invoice> context, CancellationToken ct = default)
{
return Task.CompletedTask;
}
}

// In your data access layer
public class InvoiceRepository
{
private readonly IRuleRowFilter<Invoice> _rowFilter;
private readonly ISystemExecutionContextAccessor _contextAccessor;

public async Task<List<Invoice>> GetAllAsync()
{
var context = _contextAccessor.Get();
var filterContext = new RowFilterContext<Invoice>
{
UserId = context.UserId,
TenantId = context.TenantId,
Query = _dbContext.Invoices.AsQueryable()
};

var filtered = await _rowFilter.ApplyAsync(filterContext);
return await filtered.ToListAsync();
}
}

Decision Caching

Authorization decisions are cached with a 1-minute TTL to balance security with performance. Cache keys include UserId, Resource, and Action.

authz:decision:{UserId}:{Resource}:{Action}

Muonroi.Bff

NuGet: Muonroi.Bff | Tier: Commercial | Distribution: GitHub Packages | License: Proprietary

Purpose

Implements the Backend-for-Frontend (BFF) security pattern for Single-Page Applications (SPAs). Provides server-side token management, cookie-based session handling, and CSRF protection. Refresh tokens are stored server-side and never exposed to the browser.

Key Types

TypeKindPurpose
ITokenStoreInterfaceServer-side refresh token storage abstraction.
InMemoryTokenStoreClassIn-memory token storage (development/testing).
RedisTokenStoreClassRedis-backed distributed token storage (production).

Token Store Interface

public interface ITokenStore
{
Task StoreRefreshTokenAsync(string subject, string refreshToken);
Task<string?> GetRefreshTokenAsync(string subject);
Task RemoveRefreshTokenAsync(string subject);
}

Configuration

Bind token lifetime settings from appsettings.json:

{
"Bff": {
"RefreshTokenLifetimeMinutes": 43200
},
"Authentication": {
"RefreshTokenLifetimeMinutes": 43200
}
}

Defaults to 30 days (43,200 minutes) if not configured.

DI Registration

using Muonroi.Bff;

var services = new ServiceCollection();

// Option 1: In-memory token store (development)
services.AddBffAuthentication(useRedisTokenStore: false);

// Option 2: Redis-backed token store (production)
services.AddBffAuthentication(useRedisTokenStore: true);

BFF automatically configures secure cookies:

SettingValuePurpose
HttpOnlytruePrevents JavaScript access to cookies.
SecurePolicyAlwaysRequires HTTPS transmission.
SameSiteStrictPrevents cross-site cookie submission.

Usage Example

[ApiController]
[Route("api/auth")]
public class AuthController : ControllerBase
{
private readonly ITokenStore _tokenStore;
private readonly JwtService _jwtService;

[HttpPost("login")]
public async Task<IActionResult> Login(LoginRequest request)
{
// Validate user credentials
var user = await ValidateUserAsync(request.Email, request.Password);
if (user == null)
return Unauthorized();

// Generate tokens
var accessToken = _jwtService.GenerateToken(
subject: user.Id,
lifetime: TimeSpan.FromMinutes(15));

var refreshToken = GenerateRefreshToken();

// Store refresh token server-side
await _tokenStore.StoreRefreshTokenAsync(user.Id, refreshToken);

// Return access token in response, set refresh token in secure cookie
Response.Cookies.Append("refresh_token", refreshToken,
new CookieOptions
{
HttpOnly = true,
Secure = true,
SameSite = SameSiteMode.Strict,
Expires = DateTimeOffset.UtcNow.AddDays(30)
});

return Ok(new { accessToken });
}

[HttpPost("refresh")]
public async Task<IActionResult> RefreshToken()
{
// Get refresh token from cookie (not exposed to JavaScript)
var refreshToken = Request.Cookies["refresh_token"];
if (string.IsNullOrEmpty(refreshToken))
return Unauthorized();

// Validate stored token
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var storedToken = await _tokenStore.GetRefreshTokenAsync(userId);

if (storedToken != refreshToken)
return Unauthorized();

// Issue new access token
var newAccessToken = _jwtService.GenerateToken(
subject: userId,
lifetime: TimeSpan.FromMinutes(15));

return Ok(new { accessToken = newAccessToken });
}

[HttpPost("logout")]
[Authorize]
public async Task<IActionResult> Logout()
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;

// Remove server-side refresh token
await _tokenStore.RemoveRefreshTokenAsync(userId);

// Clear cookie
Response.Cookies.Delete("refresh_token");

return NoContent();
}
}

SPA Integration Pattern

// SPA: auth.service.ts
export class AuthService {
async login(email: string, password: string) {
const response = await fetch('/api/auth/login', {
method: 'POST',
credentials: 'include', // Include cookies
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});

const data = await response.json();
// Access token stored in memory (not in cookies)
localStorage.setItem('access_token', data.accessToken);
}

async refreshAccessToken() {
// Refresh token is automatically sent via secure cookie
const response = await fetch('/api/auth/refresh', {
method: 'POST',
credentials: 'include'
});

const data = await response.json();
localStorage.setItem('access_token', data.accessToken);
}

logout() {
localStorage.removeItem('access_token');
return fetch('/api/auth/logout', {
method: 'POST',
credentials: 'include'
});
}
}

Tenant-Scoped Token Storage

RedisTokenStore automatically tenant-scopes tokens when used in a multi-tenant application:

// Tokens are stored with tenant prefix in Redis
// Key: {TenantId}:bff:refresh:{Subject}
await _tokenStore.StoreRefreshTokenAsync("user-123", "refresh-token-xyz");
// → Stores in: "tenant-456:bff:refresh:user-123" (if TenantId="tenant-456")

Comparison Matrix

FeatureMuonroi.AuthMuonroi.AuthZMuonroi.Bff
JWT Generation/Validation
Password Hashing (BCrypt)
Rule-Driven Authorization
Row-Level Security (RLS)
Hot-Reload Rules
BFF Pattern
Server-Side Token Storage
WebAuthn/FIDO2
OIDC Support

Integration Patterns

Complete Authentication + Authorization Flow

public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://auth-server.com";
options.Audience = "api";
});

// JWT token management
services.AddRedisRsaKeyStore(Configuration);

// Rule-driven authorization
services.AddMAuthorizationRuleEngine();
services.AddMAuthorizationHotReload(opts =>
{
opts.ControlPlaneUrl = Configuration["ControlPlane:Url"];
});

// Register authorization rules
services.AddScoped<IRule<AuthorizationRuleContext>, UserCanReadOrdersRule>();
services.AddScoped<IRule<AuthorizationRuleContext>, AdminCanDeleteOrdersRule>();
services.AddScoped<IRule<RowFilterContext<Order>>, UserSeeOwnOrdersRule>();
}

public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
app.UseAuthorization();

app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

SPA + BFF Pattern

public void ConfigureServices(IServiceCollection services)
{
// BFF authentication with secure cookies
services.AddBffAuthentication(useRedisTokenStore: true);

// JWT for backend token validation
services.AddRedisRsaKeyStore(Configuration);

// Authorization rules
services.AddMAuthorizationRuleEngine();
}