Substratum 1.0.0-beta.59
dotnet add package Substratum --version 1.0.0-beta.59
NuGet\Install-Package Substratum -Version 1.0.0-beta.59
<PackageReference Include="Substratum" Version="1.0.0-beta.59" />
<PackageVersion Include="Substratum" Version="1.0.0-beta.59" />
<PackageReference Include="Substratum" />
paket add Substratum --version 1.0.0-beta.59
#r "nuget: Substratum, 1.0.0-beta.59"
#:package Substratum@1.0.0-beta.59
#addin nuget:?package=Substratum&version=1.0.0-beta.59&prerelease
#tool nuget:?package=Substratum&version=1.0.0-beta.59&prerelease
Substratum
Substratum is an opinionated, production-grade application framework built on ASP.NET Core and FastEndpoints. It eliminates the boilerplate required to bootstrap a modern web API — authentication, authorization, database, caching, logging, OpenAPI docs, cloud storage, and push notifications are all pre-wired and ready to go.
Write your business logic. Substratum handles the rest.
Packages
| Package | Description | Target |
|---|---|---|
| Substratum | Core runtime library — middleware, auth, EF Core, cloud integrations | net10.0 |
| Substratum.Generator | Roslyn source generators — permissions, reflection, service registration, endpoint summaries, document groups | netstandard2.0 |
| Substratum.Tools | CLI tool (dotnet sub) — scaffold projects, endpoints, entities, and manage migrations |
net10.0 |
Quick Start
1. Install the CLI
dotnet tool install -g Substratum.Tools
2. Create a new project
dotnet sub new webapp --name MyApp
This scaffolds a complete project with authentication, permissions, EF Core, localization, and health checks — ready to run.
3. Add packages to an existing project
<PackageReference Include="Substratum" Version="1.0.0-beta.58" />
<PackageReference Include="Substratum.Generator" Version="1.0.0-beta.58" />
4. Run the app
await SubstratumApp.RunAsync<AppDbContext, AppPermissions>(args, options =>
{
options.EntityFramework.Provider = EntityFrameworkProviders.Npgsql;
options.EntityFramework.ConnectionString = "Host=localhost;Database=myapp;...";
options.Authentication.JwtBearer.Enabled = true;
options.Authentication.JwtBearer.Options = new JwtBearerOptions
{
SecretKey = "your-256-bit-secret-key",
Issuer = "https://myapp.com",
Audience = "myapp-api"
};
});
Substratum (Core Library)
Endpoints
All endpoints inherit from BaseEndpoint<TRequest, TResponse> and return a standardized Result<TResponse>.
public class GetOrderEndpoint : BaseEndpoint<GetOrderRequest, OrderResponse>
{
public override void Configure()
{
Get("/orders/{id}");
PermissionsAny(AppPermissions.OrdersGet);
}
public override async Task<Result<OrderResponse>> ExecuteAsync(GetOrderRequest req, CancellationToken ct)
{
var order = await db.Orders.FindAsync(req.Id, ct);
if (order is null)
return Failure(404, "OrderNotFound");
return Success("OrderRetrieved", new OrderResponse { Id = order.Id, Total = order.Total });
}
}
Result<T> wraps every response:
| Property | Type | Description |
|---|---|---|
Code |
int |
0 for success, 1 for failure |
Message |
string |
Human-readable (localized) message |
Data |
T? |
Response payload |
Errors |
IReadOnlyList<string>? |
Validation or error details |
For endpoints that return no data, use Unit as the response type.
Pagination
public class ListOrdersEndpoint : BaseEndpoint<ListOrdersRequest, PaginatedResult<OrderResponse>>
{
public override async Task<Result<PaginatedResult<OrderResponse>>> ExecuteAsync(
ListOrdersRequest req, CancellationToken ct)
{
var result = await PaginatedResult<OrderResponse>.CreateAsync(
db.Orders.Select(o => new OrderResponse { Id = o.Id, Total = o.Total }),
req.PageNumber,
req.PageSize,
ct
);
return Success("OrdersRetrieved", result);
}
}
PaginatedResult<T> includes PageNumber, TotalPages, TotalCount, Items, HasPreviousPage, and HasNextPage.
Base Entity
All domain entities inherit from BaseEntity<T> which provides built-in audit fields:
public sealed class Order : BaseEntity<Guid>
{
public decimal Total { get; set; }
public string Status { get; set; }
}
| Property | Type | Description |
|---|---|---|
Id |
T |
Primary key |
CreatedAt |
DateTimeOffset |
Auto-set on creation |
UpdatedAt |
DateTimeOffset |
Auto-set on update |
IsDeleted |
bool |
Soft delete flag |
DeletedAt |
DateTimeOffset? |
Soft delete timestamp |
Authentication
Substratum supports four authentication schemes out of the box. Enable any combination — the framework automatically configures a policy scheme that routes to the correct handler.
JWT Bearer
options.Authentication.JwtBearer.Enabled = true;
options.Authentication.JwtBearer.Options = new JwtBearerOptions
{
SecretKey = "your-256-bit-secret-key",
Issuer = "https://myapp.com",
Audience = "myapp-api",
Expiration = TimeSpan.FromHours(1),
RefreshExpiration = TimeSpan.FromDays(7) // optional, default 7 days
};
Create tokens with IJwtBearer:
// Access token only (no refresh token store needed)
var (accessToken, sessionId, expiration) = jwtBearer.CreateToken(userId);
Refresh Tokens
For token rotation workflows, implement IRefreshTokenStore and use CreateTokenPairAsync / RefreshAsync:
public class RefreshTokenStore : IRefreshTokenStore
{
public Task StoreAsync(Guid userId, Guid sessionId, string tokenHash,
DateTimeOffset expiration, CancellationToken ct)
{
// Store the hashed refresh token in your database
}
public Task<RefreshTokenValidationResult?> ValidateAndRevokeAsync(string tokenHash, CancellationToken ct)
{
// Find token by hash, revoke it, return userId/sessionId (or null if invalid/expired)
}
public Task RevokeBySessionAsync(Guid sessionId, CancellationToken ct)
{
// Revoke all refresh tokens for a session (logout)
}
public Task RevokeAllAsync(Guid userId, CancellationToken ct)
{
// Revoke all refresh tokens for a user (logout everywhere)
}
}
Issue and refresh token pairs:
// Issue access + refresh token pair
var (accessToken, refreshToken, sessionId, accessExpiration, refreshExpiration) =
await jwtBearer.CreateTokenPairAsync(userId, ct);
// Refresh — atomically revokes the old refresh token and issues a new pair
var result = await jwtBearer.RefreshAsync(refreshToken, ct);
if (result is null)
return Failure(401, "InvalidRefreshToken");
var (newAccessToken, newRefreshToken, newAccessExpiration, newRefreshExpiration) = result.Value;
Refresh tokens are SHA256-hashed before storage — only hashes are persisted, never raw tokens. RefreshAsync performs atomic token rotation: the old token is revoked and a new pair is issued in a single operation.
Cookie
options.Authentication.Cookie.Enabled = true;
options.Authentication.Cookie.Options = new CookieOptions
{
CookieName = "auth_token",
Expiration = TimeSpan.FromHours(1),
SlidingExpiration = true,
SameSite = SameSiteMode.Strict
};
Sign in/out with ICookieAuth:
var (sessionId, expiration) = await cookieAuth.SignInAsync(HttpContext, userId, ct);
await cookieAuth.SignOutAsync(HttpContext, ct);
Basic Authentication
options.Authentication.BasicAuthentication.Enabled = true;
options.Authentication.BasicAuthentication.Options = new BasicAuthenticationOptions
{
Realm = "MyAPI"
};
Implement IBasicAuthValidator:
public class BasicAuthValidator : IBasicAuthValidator
{
public async Task<(bool Result, string UserId, string SessionId)> ValidateAsync(
HttpContext context, string username, string password, CancellationToken ct)
{
var user = await db.Users.FirstOrDefaultAsync(u => u.Email == username, ct);
if (user is null || !passwordHasher.VerifyHashedPassword(user.PasswordHash, password, out _))
return (false, "", "");
return (true, user.Id.ToString("N"), Guid.CreateVersion7().ToString("N"));
}
}
Access Key
options.Authentication.AccessKeyAuthentication.Enabled = true;
options.Authentication.AccessKeyAuthentication.Options = new AccessKeyAuthenticationOptions
{
KeyName = "X-API-KEY",
Realm = "MyAPI"
};
Implement IAccessKeyValidator:
public class AccessKeyValidator : IAccessKeyValidator
{
public async Task<(bool Result, string UserId, string SessionId)> ValidateAsync(
HttpContext context, string accessKey, CancellationToken ct)
{
var key = await db.AccessKeys.FirstOrDefaultAsync(k => k.Key == accessKey && k.IsActive, ct);
if (key is null) return (false, "", "");
return (true, key.UserId.ToString("N"), Guid.CreateVersion7().ToString("N"));
}
}
Supporting Interfaces
| Interface | Purpose | Required By |
|---|---|---|
ISessionValidator |
Validates active sessions server-side | JWT, Cookie |
IPermissionHydrator |
Loads user permissions into claims | All schemes |
IRefreshTokenStore |
Stores and validates hashed refresh tokens | JWT (when using refresh tokens) |
IPasswordHasher |
Hashes and verifies passwords (PBKDF2, 600k iterations) | Built-in service |
ITotpProvider |
TOTP 2FA — generate secrets, QR URIs, validate codes | Built-in service |
ICurrentUser |
Access current user's ID (Guid?) |
Built-in service |
Two-Factor Authentication (TOTP)
Substratum provides ITotpProvider — a built-in TOTP service compatible with Google Authenticator, Authy, and other authenticator apps.
Enrollment:
// Generate a secret and QR code URI for the user
var secret = totpProvider.GenerateSecret();
var qrCodeUri = totpProvider.GenerateQrCodeUri(secret, user.Email, "MyApp");
// Store secret in your database, return qrCodeUri to the client for QR display
Verification:
// Validate a 6-digit code from the authenticator app
var isValid = totpProvider.ValidateCode(user.TotpSecret, request.Code);
Login flow with 2FA:
- Validate password
- If 2FA not enabled → issue token via
jwtBearer.CreateToken()orCreateTokenPairAsync() - If 2FA enabled → return a pending state (no token yet)
- User submits TOTP code → validate with
totpProvider.ValidateCode()→ issue token
The framework provides the TOTP utility; you implement the enrollment endpoints and login flow in your own endpoints since these are app-specific.
Permissions
Define permissions as static fields in a partial class implementing IPermissionRegistry. The source generator auto-generates Definitions(), Parse(), and TryParse() methods.
public partial class AppPermissions : IPermissionRegistry
{
public static readonly PermissionDefinition OrdersGet;
public static readonly PermissionDefinition OrdersCreate;
public static readonly PermissionDefinition OrdersEdit;
public static readonly PermissionDefinition OrdersDelete;
public static readonly PermissionDefinition UsersGet;
public static readonly PermissionDefinition UsersCreate;
}
The generator produces:
- Code: unique short code derived from the field name (e.g.
A1B2) - Name: snake_case from the field name (e.g.
orders_get) - DisplayName: human-readable (e.g.
Get) - Group: inferred from the field name prefix (e.g.
Ordersgroup)
Use permissions in endpoints:
public override void Configure()
{
Get("/orders/{id}");
PermissionsAny(AppPermissions.OrdersGet); // user needs ANY of these
PermissionsAll(AppPermissions.OrdersGet); // user needs ALL of these
}
Implement IPermissionHydrator to load permissions from your database into claims:
public class PermissionHydrator : IPermissionHydrator
{
public async Task HydrateAsync(IServiceProvider sp, ClaimsPrincipal principal, CancellationToken ct)
{
var userId = principal.FindFirstValue(SubstratumClaimsTypes.UserId);
var db = sp.GetRequiredService<AppDbContext>();
var codes = await db.UserPermissions
.Where(up => up.UserId == Guid.Parse(userId))
.Select(up => up.Permission.Code)
.ToListAsync(ct);
var identity = (ClaimsIdentity)principal.Identity!;
foreach (var code in codes)
identity.AddClaim(new Claim("permissions", code));
}
}
Document Groups
Organize your API into separate OpenAPI documents — each with its own Scalar UI page and optional authentication.
Define groups in Common/Security/DocGroups.cs:
public partial class DocGroups : IDocGroupRegistry
{
// Default group — shows ALL endpoints at /docs (no tag filtering required)
public static readonly DocGroupDefinition Default =
new("API Documentation", "docs", isDefault: true);
// Filtered groups — show only tagged endpoints
public static readonly DocGroupDefinition Mobile =
new("Mobile API", "mobile");
public static readonly DocGroupDefinition Admin =
new("Admin API", "admin", AppPermissions.AdminDocsAccess); // protected with permission
}
Use in endpoints:
public override void Configure()
{
Get("/orders");
DocGroup(DocGroups.Mobile);
}
This creates:
/docs— Scalar UI showing all endpoints (the Default group)/mobile— Scalar UI showing only Mobile API endpoints/admin— Scalar UI showing only Admin API endpoints (requires API key)
How it works:
- The
Defaultgroup (isDefault: true) shows all endpoints regardless of tags. Endpoints don't need to callDocGroup()to appear in it. If no Default group is defined, there is no "all endpoints" documentation page. - Non-default groups only show endpoints explicitly tagged via
DocGroup(). - Protected groups (with a
PermissionDefinition) use Basic Auth — the username is the access key, the password is empty. The browser's native auth dialog prompts the user. Permissions are validated throughIAccessKeyValidatorandIPermissionHydrator.
Entity Framework
options.EntityFramework.Provider = EntityFrameworkProviders.Npgsql; // or SqlServer, Sqlite
options.EntityFramework.ConnectionString = "Host=localhost;Database=myapp;...";
Supported providers: PostgreSQL, SQL Server, SQLite
Retry Policy
options.EntityFramework.RetryPolicy.Enabled = true;
options.EntityFramework.RetryPolicy.Options = new RetryPolicyOptions
{
MaxRetryCount = 3,
MaxRetryDelaySeconds = 2
};
Second-Level Cache
options.EntityFramework.SecondLevelCache.Enabled = true;
options.EntityFramework.SecondLevelCache.Options = new SecondLevelCacheOptions
{
Provider = SecondLevelCacheProviders.Memory, // or Redis
KeyPrefix = "app:"
};
// Redis configuration
options.EntityFramework.SecondLevelCache.Options.Redis = new SecondLevelCacheRedisOptions
{
ConnectionString = "localhost:6379",
TimeoutSeconds = 300
};
OpenAPI Documentation
options.OpenApi.Enabled = true;
options.OpenApi.Options.Servers = new[]
{
new OpenApiServerOptions { Url = "https://api.example.com", Description = "Production" },
new OpenApiServerOptions { Url = "https://staging.example.com", Description = "Staging" }
};
Substratum generates Scalar UI at /docs (or at document group URLs). Features include:
- Automatic permission documentation on each endpoint
- Accept-Language header parameter for localized APIs
- Dark mode theme
Localization
options.Localization.DefaultCulture = "en";
options.Localization.SupportedCultures = ["en", "ar", "fr"];
options.Localization.ResourceSource = typeof(SharedResource);
The framework reads Accept-Language headers and localizes validation messages, error responses, and OpenAPI descriptions.
Cloud Storage
MinIO (S3-Compatible)
options.Minio.Enabled = true;
options.Minio.Options = new MinioOptions
{
Endpoint = "minio.example.com",
Region = "us-east-1",
Secure = true,
AccessKey = "...",
SecretKey = "..."
};
Inject IMinioClient to interact with S3-compatible storage.
AWS S3
options.Aws.S3.Enabled = true;
options.Aws.S3.Options = new AwsS3Options
{
Region = "us-west-2",
AccessKey = "...",
SecretKey = "..."
};
Inject IAmazonS3 to interact with AWS S3.
AWS Secrets Manager
options.Aws.SecretsManager.Enabled = true;
options.Aws.SecretsManager.Options = new AwsSecretsManagerOptions
{
Region = "us-west-2",
AccessKey = "...",
SecretKey = "...",
SecretArns = ["arn:aws:secretsmanager:us-west-2:123456789:secret:my-secret"]
};
Secrets are automatically loaded into IConfiguration.
Firebase
Cloud Messaging
options.Firebase.Messaging.Enabled = true;
options.Firebase.Messaging.Options = new FirebaseMessagingOptions
{
Credential = Convert.ToBase64String(Encoding.UTF8.GetBytes(serviceAccountJson))
};
Inject FirebaseMessaging to send push notifications.
App Check
options.Firebase.AppCheck.Enabled = true;
options.Firebase.AppCheck.Options = new FirebaseAppCheckOptions
{
ProjectId = "my-project",
ProjectNumber = "123456789"
};
Inject IFirebaseAppCheck and call VerifyAppCheckTokenAsync().
Infrastructure
| Feature | Configuration | Default |
|---|---|---|
| CORS | options.Cors.AllowedOrigins = [...] |
Disabled |
| Health Checks | options.HealthChecks.Enabled = true |
/healthz |
| Static Files | options.StaticFiles.Enabled = true |
wwwroot |
| Response Compression | Always on | Gzip + Brotli |
| Forwarded Headers | Always on | X-Forwarded-For/Proto |
| Logging | Serilog via appsettings.json |
Console sink, sensitive data masking |
| Kestrel Limits | Pre-configured | 1 MB body, 10k connections, 15s header timeout |
| Distributed Cache | options.DistributedCache.Enabled = true |
InMemory or Redis |
Distributed Cache
options.DistributedCache.Enabled = true;
options.DistributedCache.Options = new DistributedCacheOptions
{
Provider = DistributedCacheProviders.Redis, // or Memory
Redis = new DistributedCacheRedisOptions
{
ConnectionString = "localhost:6379",
InstanceName = "DC_"
}
};
Inject IDistributedCache to use the cache. When Provider is Memory, an in-memory cache is registered (useful for development). When Provider is Redis, a StackExchange Redis-backed cache is registered.
Substratum.Generator
Seven Roslyn incremental source generators that eliminate boilerplate at compile time.
1. SubstratumApp Generator
Generates a [ModuleInitializer] that wires up the framework. Scans for:
DbContextimplementationIPermissionRegistryimplementationISessionValidator,IPermissionHydrator,IBasicAuthValidator,IAccessKeyValidator,IRefreshTokenStoreimplementations
Output: SubstratumAppInitializer.g.cs
2. Discovered Types Generator
Scans for all concrete classes implementing FastEndpoints interfaces (IEndpoint, IEventHandler, ICommandHandler, ISummary, IValidator) and collects them into a type list. Respects [DontRegister].
Output: DiscoveredTypes.g.cs
3. Service Registration Generator
Scans for [RegisterService<TInterface>(ServiceLifetime)] attributes and generates DI extension methods.
[RegisterService<IOrderService>(ServiceLifetime.Scoped)]
public class OrderService : IOrderService { }
Output: ServiceRegistrations.g.cs with RegisterServicesFromMyApp() extension method.
4. Reflection Generator
Builds a FastEndpoints reflection cache by analyzing endpoint request/response DTOs — object factories, property setters, and value parsers. Eliminates runtime reflection.
Output: ReflectionData.g.cs
5. Permissions Generator
Generates the Definitions(), Parse(), and TryParse() methods for IPermissionRegistry implementations. Auto-computes permission codes, names, display names, and groups from field names.
Output: {ClassName}.Permissions.g.cs
6. Endpoint Summary Generator
Scans BaseEndpoint<TRequest, TResponse> subclasses and generates OpenAPI summary classes. Auto-detects:
- Error responses from
Failure()calls - 400 if a
Validator<TRequest>exists - 401 if the endpoint is not
AllowAnonymous() - 403 if permissions are declared
Output: {EndpointName}Summary.g.cs
7. Document Group Generator
Generates Definitions() and a [ModuleInitializer] for IDocGroupRegistry implementations.
Output: {ClassName}.DocGroups.g.cs
Substratum.Tools (CLI)
Install globally:
dotnet tool install -g Substratum.Tools
Commands
dotnet sub new webapp
Scaffold a complete web application:
dotnet sub new webapp --name MyApp
Generates a full project with:
- Pre-configured
Program.cswithSubstratumApp.RunAsync AppDbContextwith User, Role, UserSession entitiesAppPermissionsregistrySessionValidatorandPermissionHydrator- Localization resources (EN, AR)
- Initial EF migration
AGENTS.mdwith AI scaffolding guidelines
dotnet sub new endpoint
Scaffold a new API endpoint:
dotnet sub new endpoint \
--group Orders \
--name CreateOrder \
--route /orders \
--method Post \
--permission OrdersCreate \
--response-type SingleResult
Generates under Features/Orders/CreateOrder/:
CreateOrderEndpoint.cs— endpoint handlerCreateOrderRequest.cs— request DTOCreateOrderResponse.cs— response DTOCreateOrderRequestValidator.cs— FluentValidation validatorCreateOrderSummary.cs— OpenAPI summaryCreateOrderSerializerContext.cs— JSON source-gen context
Also inserts the permission definition into AppPermissions.cs automatically.
For paginated endpoints, use --response-type PaginatedResult — the request will include PageNumber and PageSize properties.
All options are interactive — omit any flag and the CLI will prompt you.
dotnet sub new entity
Scaffold a domain entity:
dotnet sub new entity --name Order
Generates:
Domain/Entities/Order.cs— entity class inheritingBaseEntity<Guid>Data/Configurations/OrderConfiguration.cs— EF Core configuration
Also inserts public DbSet<Order> Orders => Set<Order>(); into AppDbContext.cs.
dotnet sub migrations add
Add an EF Core migration:
dotnet sub migrations add AddOrderTable
dotnet sub database update
Apply pending migrations:
dotnet sub database update
dotnet sub database sql
Generate an idempotent SQL script:
dotnet sub database sql -o ./deploy.sql
Configuration Reference
await SubstratumApp.RunAsync<AppDbContext, AppPermissions>(args, options =>
{
// Authentication
options.Authentication.JwtBearer.Enabled = true;
options.Authentication.JwtBearer.Options = new JwtBearerOptions { ... };
options.Authentication.Cookie.Enabled = true;
options.Authentication.Cookie.Options = new CookieOptions { ... };
options.Authentication.BasicAuthentication.Enabled = true;
options.Authentication.BasicAuthentication.Options = new BasicAuthenticationOptions { ... };
options.Authentication.AccessKeyAuthentication.Enabled = true;
options.Authentication.AccessKeyAuthentication.Options = new AccessKeyAuthenticationOptions { ... };
// Database
options.EntityFramework.Provider = EntityFrameworkProviders.Npgsql;
options.EntityFramework.ConnectionString = "...";
options.EntityFramework.CommandTimeoutSeconds = 30;
options.EntityFramework.RetryPolicy.Enabled = true;
options.EntityFramework.RetryPolicy.Options = new RetryPolicyOptions { ... };
options.EntityFramework.SecondLevelCache.Enabled = true;
options.EntityFramework.SecondLevelCache.Options = new SecondLevelCacheOptions { ... };
// Localization
options.Localization.DefaultCulture = "en";
options.Localization.SupportedCultures = ["en", "ar"];
options.Localization.ResourceSource = typeof(SharedResource);
// OpenAPI
options.OpenApi.Enabled = true;
options.OpenApi.Options.Servers = [new OpenApiServerOptions { ... }];
// CORS
options.Cors.AllowedOrigins = ["https://example.com"];
// Health Checks
options.HealthChecks.Enabled = true;
options.HealthChecks.Options = new HealthChecksOptions
{
Path = "/healthz",
HealthChecksBuilder = hc => hc.AddDbContextCheck<AppDbContext>()
};
// Static Files
options.StaticFiles.Enabled = true;
options.StaticFiles.Options = new StaticFilesOptions { RootPath = "wwwroot" };
// Error Handling
options.ErrorHandling.IncludeExceptionDetails = false;
// Cloud — MinIO
options.Minio.Enabled = true;
options.Minio.Options = new MinioOptions { ... };
// Cloud — AWS
options.Aws.S3.Enabled = true;
options.Aws.S3.Options = new AwsS3Options { ... };
options.Aws.SecretsManager.Enabled = true;
options.Aws.SecretsManager.Options = new AwsSecretsManagerOptions { ... };
// Firebase
options.Firebase.Messaging.Enabled = true;
options.Firebase.Messaging.Options = new FirebaseMessagingOptions { ... };
options.Firebase.AppCheck.Enabled = true;
options.Firebase.AppCheck.Options = new FirebaseAppCheckOptions { ... };
// Distributed Cache
options.DistributedCache.Enabled = true;
options.DistributedCache.Options = new DistributedCacheOptions
{
Provider = DistributedCacheProviders.Redis,
Redis = new DistributedCacheRedisOptions { ConnectionString = "...", InstanceName = "DC_" }
};
// Custom Services
options.Services.AddScoped<IMyService, MyService>();
});
Interfaces to Implement
| Interface | Purpose | When Required |
|---|---|---|
IPermissionRegistry |
Define all permissions | Always (source-generated) |
IDocGroupRegistry |
Define API document groups | When using document groups (source-generated) |
ISessionValidator |
Validate active sessions | JWT or Cookie auth |
IPermissionHydrator |
Load user permissions into claims | When using permissions |
IBasicAuthValidator |
Validate username/password | Basic auth enabled |
IAccessKeyValidator |
Validate access keys | Access key auth or protected document groups |
IRefreshTokenStore |
Store and validate refresh tokens | JWT with refresh token rotation |
All implementations are auto-discovered by the source generators — no manual registration needed.
License
See LICENSE for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- AspNetCore.Authentication.ApiKey (>= 9.0.0)
- AWSSDK.S3 (>= 4.0.18.3)
- AWSSDK.SecretsManager (>= 4.0.4.6)
- EFCore.CheckConstraints (>= 10.0.0)
- EFCore.NamingConventions (>= 10.0.1)
- EFCoreSecondLevelCacheInterceptor (>= 5.3.9)
- EFCoreSecondLevelCacheInterceptor.MemoryCache (>= 5.3.9)
- EFCoreSecondLevelCacheInterceptor.StackExchange.Redis (>= 5.3.9)
- FastEndpoints (>= 7.2.0)
- FastEndpoints.Swagger (>= 7.2.0)
- FirebaseAdmin (>= 3.4.0)
- FluentValidation (>= 12.1.1)
- Kralizek.Extensions.Configuration.AWSSecretsManager (>= 1.7.0)
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 10.0.2)
- Microsoft.EntityFrameworkCore (>= 10.0.2)
- Microsoft.EntityFrameworkCore.InMemory (>= 10.0.2)
- Microsoft.EntityFrameworkCore.Relational (>= 10.0.2)
- Microsoft.EntityFrameworkCore.Sqlite (>= 10.0.2)
- Microsoft.EntityFrameworkCore.SqlServer (>= 10.0.2)
- Microsoft.Extensions.Caching.StackExchangeRedis (>= 10.0.2)
- Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore (>= 10.0.2)
- Minio (>= 7.0.0)
- Npgsql.EntityFrameworkCore.PostgreSQL (>= 10.0.0)
- Otp.NET (>= 1.4.1)
- Scalar.AspNetCore (>= 2.12.36)
- Serilog (>= 4.3.0)
- Serilog.AspNetCore (>= 10.0.0)
- Serilog.Enrichers.Sensitive (>= 2.1.0)
- ZNetCS.AspNetCore.Authentication.Basic (>= 10.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0-beta.59 | 9 | 2/9/2026 |
| 1.0.0-beta.58 | 18 | 2/9/2026 |
| 1.0.0-beta.56 | 26 | 2/9/2026 |
| 1.0.0-beta.55 | 27 | 2/9/2026 |
| 1.0.0-beta.54 | 32 | 2/8/2026 |
| 1.0.0-beta.53 | 33 | 2/7/2026 |
| 1.0.0-beta.52 | 31 | 2/7/2026 |
| 1.0.0-beta.51 | 30 | 2/7/2026 |
| 1.0.0-beta.50 | 29 | 2/7/2026 |
| 1.0.0-beta.49 | 29 | 2/7/2026 |
| 1.0.0-beta.48 | 30 | 2/7/2026 |
| 1.0.0-beta.47 | 28 | 2/7/2026 |
| 1.0.0-beta.46 | 29 | 2/7/2026 |
| 1.0.0-beta.45 | 34 | 2/7/2026 |
| 1.0.0-beta.44 | 39 | 2/7/2026 |
| 1.0.0-beta.43 | 38 | 2/7/2026 |
| 1.0.0-beta.42 | 27 | 2/7/2026 |
| 1.0.0-beta.41 | 33 | 2/7/2026 |
| 1.0.0-beta.40 | 32 | 2/6/2026 |
| 1.0.0-beta.30 | 26 | 2/6/2026 |
| 1.0.0-beta.24 | 31 | 2/6/2026 |
| 1.0.0-beta.21 | 31 | 2/6/2026 |
| 1.0.0-beta.20 | 37 | 2/6/2026 |
| 1.0.0-beta.7 | 44 | 2/1/2026 |
| 1.0.0-beta.6 | 47 | 1/21/2026 |
| 1.0.0-beta.5 | 46 | 1/21/2026 |
| 1.0.0-beta.4 | 48 | 1/20/2026 |
| 1.0.0-beta.3 | 47 | 1/20/2026 |
| 1.0.0-beta.2 | 49 | 1/20/2026 |
| 1.0.0-beta.1 | 50 | 1/18/2026 |