WiseAuth 0.0.0-alpha.0.9
dotnet add package WiseAuth --version 0.0.0-alpha.0.9
NuGet\Install-Package WiseAuth -Version 0.0.0-alpha.0.9
<PackageReference Include="WiseAuth" Version="0.0.0-alpha.0.9" />
<PackageVersion Include="WiseAuth" Version="0.0.0-alpha.0.9" />
<PackageReference Include="WiseAuth" />
paket add WiseAuth --version 0.0.0-alpha.0.9
#r "nuget: WiseAuth, 0.0.0-alpha.0.9"
#:package WiseAuth@0.0.0-alpha.0.9
#addin nuget:?package=WiseAuth&version=0.0.0-alpha.0.9&prerelease
#tool nuget:?package=WiseAuth&version=0.0.0-alpha.0.9&prerelease
WiseAuth
A lightweight ASP.NET Core authorization library that uses power-of-two bit-flag enums and bitwise claim checking to enforce per-endpoint permissions.
How it works
- You define a permission enum where every value is a power of two (1, 2, 4, 8, 16, …).
- At startup,
AddWiseAuth<T>()validates the enum and registers anIAuthorizationHandlerfor it. - Each route is decorated with
.EndpointId<T>(value), which attaches anIAuthorizationRequirementcontaining that value. - On each request, the handler reads the user's claim, interprets it as a bitmask, and checks
(claimValue & endpointId) != 0.
Because each permission occupies its own bit, any combination of permissions can be encoded as a single integer (the OR/sum of the selected values) and individual permissions can be checked with a single AND operation without colliding with any other combination.
Getting started
1. Define a permission enum
Values must form a power-of-two sequence starting at 1 with no gaps (1, 2, 4, 8, 16, …). The order of members in the enum does not matter; the validator sorts them before checking.
public enum ProductPermissions
{
Read = 1,
Write = 2,
Delete = 4,
Export = 8,
}
If the enum is nested inside a controller class, the claim type is derived automatically by stripping the Controller suffix. For a fully custom name, apply [ClaimType]:
public class ProductController
{
// Claim type → "ProductPermissions" (Controller suffix stripped)
public enum Permissions
{
Read = 1,
Write = 2,
Delete = 4,
}
}
[ClaimType("products")]
public enum ProductPermissions { ... } // Claim type → "products"
2. Register with the DI container
Call AddWiseAuth<T>() once per enum in Program.cs. An exception is thrown at startup if the enum values are not a valid power-of-two sequence.
builder.Services.AddAuthentication(...);
builder.Services.AddAuthorization();
builder.Services.AddWiseAuth<ProductController.Permissions>();
builder.Services.AddWiseAuth<OrderPermissions>();
Multiple calls are safe — IWiseAuthService is registered as a singleton only once via TryAddSingleton.
3. Protect routes
Minimal APIs
Use the .EndpointId<T>() extension method on any RouteHandlerBuilder. This attaches the requirement metadata and calls RequireAuthorization() automatically.
app.MapGet("/products", GetProducts)
.EndpointId(ProductController.Permissions.Read);
app.MapPost("/products", CreateProduct)
.EndpointId(ProductController.Permissions.Write);
app.MapDelete("/products/{id}", DeleteProduct)
.EndpointId(ProductController.Permissions.Delete);
Attribute-routed controllers
For [ApiController]-based controllers, apply [EndpointId<T>(value)] to an action (or the whole controller). It derives from AuthorizeAttribute and attaches the same requirement the minimal-API extension does, so it's enforced by the same registered IAuthorizationHandler<T> — no other setup differs.
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
[HttpGet]
[EndpointId<ProductController.Permissions>(ProductController.Permissions.Read)]
public IActionResult GetProducts() => Ok(...);
[HttpPost]
[EndpointId<ProductController.Permissions>(ProductController.Permissions.Write)]
public IActionResult CreateProduct(CreateProductRequest request) => Created(...);
}
4. Issue claims
When issuing tokens, store the user's permissions as the sum (equivalently, the bitwise OR) of their allowed power-of-two values for each claim type:
// User can Read (1) and Export (8) → claim value = 1 + 8 = 9
new Claim("ProductPermissions", "9")
The handler checks (claimValue & endpointId) != 0. This works correctly because each permission occupies a distinct, non-overlapping bit: since every value is a power of two, no combination of permissions can ever produce the same bit pattern as a different permission or combination.
Backing types and permission limits
C# enums default to int as their backing type, which supports up to 31 permissions (the sign bit can't be used for a flag). You can explicitly specify a larger backing type to increase this limit:
public enum ProductPermissions : ulong // supports up to 64 permissions
{
Read = 1,
Write = 2,
Delete = 4,
Export = 8,
}
The authorization handler always compares values as ulong internally, so upgrading the backing type requires no other changes.
| Backing type | Max value | Max permissions |
|---|---|---|
sbyte |
127 | 7 |
byte |
255 | 8 |
short |
32,767 | 15 |
ushort |
65,535 | 16 |
int (default) |
2,147,483,647 | 31 |
uint |
4,294,967,295 | 32 |
long |
9,223,372,036,854,775,807 | 63 |
ulong |
18,446,744,073,709,551,615 | 64 |
Claim type naming conventions
When no [ClaimType] attribute is present, the claim type is derived from the enum's fully-qualified name:
| Enum location | Example | Claim type |
|---|---|---|
| Top-level | StandaloneEnum |
StandaloneEnum |
| Nested in a non-controller class | Widget+Permissions |
WidgetPermissions |
| Nested in a controller class | ProductController+Permissions |
ProductPermissions |
| Custom attribute | [ClaimType("products")] |
products |
Discovering permissions at runtime
IWiseAuthService exposes all registered enum details, useful for generating documentation or admin UIs:
app.MapGet("/auth/permissions", (IWiseAuthService wiseAuth) =>
{
return wiseAuth.GetDetails();
});
// Returns: { "ProductPermissions": [{ "id": 1, "name": "Read" }, ...] }
Enum validation rules
AddWiseAuth<T>() enforces these constraints at startup — an invalid enum throws immediately rather than failing silently at runtime:
- All values must be greater than 0.
- The lowest value must be 1.
- No duplicate values.
- Values must form a complete power-of-two sequence with no gaps (e.g., skipping 4 in the sequence 1, 2, 8 is invalid).
Sample project
WiseAuth.Sample is a runnable end-to-end demo: an ASP.NET Core API + embedded React app, SQLite via EF Core, ASP.NET Identity, and three seeded users (Admin, full access; Viewer, read-only; Auditor, read-only plus one extra permission) so you can see WiseAuth both allow and deny requests. It includes a small RBAC-style Security area — Users, Roles (native ASP.NET Identity roles), and a read-only Access Controls view — with three independently-registered permission enums composing on one principal, and a custom IUserClaimsPrincipalFactory that merges a user's role-granted and individually-granted permissions into the single effective claim WiseAuth actually enforces.
Requirements
- .NET 10.0
Microsoft.AspNetCore.App
License
GPL-3.0-only
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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. net11.0 is compatible. |
-
net10.0
- No dependencies.
-
net11.0
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
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 |
|---|---|---|
| 0.0.0-alpha.0.9 | 79 | 7/23/2026 |
| 0.0.0-alpha.0.8 | 60 | 7/18/2026 |
| 0.0.0-alpha.0.7 | 59 | 7/18/2026 |