DotNetAuthCore 1.0.0
dotnet add package DotNetAuthCore --version 1.0.0
NuGet\Install-Package DotNetAuthCore -Version 1.0.0
<PackageReference Include="DotNetAuthCore" Version="1.0.0" />
<PackageVersion Include="DotNetAuthCore" Version="1.0.0" />
<PackageReference Include="DotNetAuthCore" />
paket add DotNetAuthCore --version 1.0.0
#r "nuget: DotNetAuthCore, 1.0.0"
#:package DotNetAuthCore@1.0.0
#addin nuget:?package=DotNetAuthCore&version=1.0.0
#tool nuget:?package=DotNetAuthCore&version=1.0.0
DotNetAuthCore
Authentication and authorization helpers for JWT validation, token handling, role/permission-based auth, and multi-tenant support.
Problems Solved
- JWT validation duplicated: JWT validation code repeated across projects
- Token expiration handling: Token expiration not handled gracefully
- Role-based auth too rigid: Roles too coarse-grained for fine-grained permissions
- Permission-based auth missing: No standard way to implement permission-based authorization
- Claims inconsistency: Claims accessed inconsistently across codebase
- Multi-tenant auth leaks: Users can access other tenants' data
- Auth logic untestable: Authentication logic hard to test in isolation
Installation
dotnet add package DotNetAuthCore
Quick Start
1. JWT Token Validation
Problem: JWT validation code duplicated, token expiration not handled.
using DotNetAuthCore;
using Microsoft.IdentityModel.Tokens;
// ✅ GOOD: Centralized JWT validation
var jwtHelper = new JwtHelper(new JwtHelperOptions
{
Issuer = "https://your-issuer.com",
Audience = "your-audience",
SigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)),
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5)
});
// Validate token
var principal = jwtHelper.ValidateToken(token);
if (principal == null)
{
// Token is invalid or expired
return Unauthorized();
}
// Check if token is expired
if (jwtHelper.IsTokenExpired(token))
{
return Unauthorized("Token has expired");
}
// Get claims
var claims = jwtHelper.GetClaims(token);
var userId = claims.FirstOrDefault(c => c.Type == "sub")?.Value;
2. Permission-Based Authorization
Problem: Role-based auth is too rigid, need fine-grained permissions.
// ❌ BAD: Role-based (too coarse)
[Authorize(Roles = "Admin")] // Can do everything
public class UsersController : ControllerBase { }
// ✅ GOOD: Permission-based (fine-grained)
[PermissionAuthorize("users:read", "users:write")]
public class UsersController : ControllerBase
{
[HttpDelete("{id}")]
[PermissionAuthorize("users:delete")] // Specific permission
public async Task<IActionResult> DeleteUser(int id)
{
// Only users with "users:delete" permission can access
await _userService.DeleteUserAsync(id);
return NoContent();
}
}
3. Check Permissions in Code
Problem: Need to check permissions programmatically, not just via attributes.
public class UserService
{
private readonly IHttpContextAccessor _httpContextAccessor;
public async Task DeleteUserAsync(int userId)
{
var user = _httpContextAccessor.HttpContext?.User;
// ✅ GOOD: Check permission in code
if (!user.HasPermission("users:delete"))
{
throw new UnauthorizedException("You don't have permission to delete users");
}
// Check multiple permissions
if (!user.HasAnyPermission("users:delete", "admin:all"))
{
throw new UnauthorizedException();
}
await _repository.DeleteAsync(userId);
}
}
4. Multi-Tenant Support
Problem: Users can access other tenants' data if not properly scoped.
public class OrderService
{
private readonly IHttpContextAccessor _httpContextAccessor;
public async Task<Order> GetOrderAsync(int orderId)
{
var user = _httpContextAccessor.HttpContext?.User;
var userTenantId = user.GetTenantId();
var order = await _repository.FindAsync(orderId);
// ✅ GOOD: Verify tenant access
if (order.TenantId != userTenantId)
{
throw new UnauthorizedException("Access denied to this order");
}
// Or use extension method
if (!user.IsInTenant(order.TenantId))
{
throw new UnauthorizedException();
}
return order;
}
}
Real-World Example
// JWT Configuration
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "https://your-issuer.com",
ValidAudience = "your-audience",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey))
};
});
// Register JWT helper
builder.Services.AddSingleton<JwtHelper>(sp =>
{
var configuration = sp.GetRequiredService<IConfiguration>();
return new JwtHelper(new JwtHelperOptions
{
Issuer = configuration["Jwt:Issuer"],
Audience = configuration["Jwt:Audience"],
SigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(configuration["Jwt:SecretKey"]))
});
});
// Controller with permission-based auth
[ApiController]
[Route("api/[controller]")]
[PermissionAuthorize("orders:read")] // Base permission for controller
public class OrdersController : ControllerBase
{
private readonly IOrderService _orderService;
private readonly IHttpContextAccessor _httpContextAccessor;
[HttpGet]
public async Task<ActionResult<List<Order>>> GetOrders()
{
// User already has "orders:read" from controller attribute
var orders = await _orderService.GetOrdersAsync();
return Ok(orders);
}
[HttpPost]
[PermissionAuthorize("orders:create")]
public async Task<ActionResult<Order>> CreateOrder(CreateOrderRequest request)
{
var order = await _orderService.CreateOrderAsync(request);
return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
}
[HttpPut("{id}")]
[PermissionAuthorize("orders:update")]
public async Task<IActionResult> UpdateOrder(int id, UpdateOrderRequest request)
{
await _orderService.UpdateOrderAsync(id, request);
return NoContent();
}
[HttpDelete("{id}")]
[PermissionAuthorize("orders:delete")]
public async Task<IActionResult> DeleteOrder(int id)
{
// Check permission in code as well
var user = _httpContextAccessor.HttpContext?.User;
if (!user.HasPermission("orders:delete"))
{
return Forbid();
}
await _orderService.DeleteOrderAsync(id);
return NoContent();
}
}
// Service with multi-tenant support
public class OrderService
{
private readonly IOrderRepository _repository;
private readonly IHttpContextAccessor _httpContextAccessor;
public async Task<Order> GetOrderAsync(int orderId)
{
var user = _httpContextAccessor.HttpContext?.User;
var tenantId = user.GetTenantId();
var order = await _repository.FindAsync(orderId);
// Verify tenant access
if (!user.IsInTenant(order.TenantId))
{
throw new UnauthorizedException("Access denied");
}
return order;
}
public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
{
var user = _httpContextAccessor.HttpContext?.User;
// Check permission
if (!user.HasPermission("orders:create"))
{
throw new UnauthorizedException();
}
var order = new Order
{
TenantId = user.GetTenantId(), // Set tenant from user
UserId = user.FindFirst("sub")?.Value,
// ... other properties
};
return await _repository.CreateAsync(order);
}
}
JWT Token Claims Structure
For permission-based auth, include permissions in JWT claims:
{
"sub": "user-123",
"tenant_id": "tenant-456",
"permission": ["orders:read", "orders:create", "users:read"]
}
Best Practices
- Use permission-based auth instead of roles for fine-grained control
- Validate JWT tokens using
JwtHelperfor consistency - Check tenant access for all multi-tenant operations
- Use permission attributes on controllers and actions
- Verify permissions in code for dynamic authorization
- Handle token expiration gracefully with proper error messages
- Include tenant ID in JWT claims for multi-tenant apps
API Reference
JwtHelper- Centralized JWT token validationJwtHelperOptions- Configuration for JWT validationPermissionAuthorizeAttribute- Attribute for permission-based authorizationClaimsPrincipalExtensions.HasPermission()- Check if user has permissionClaimsPrincipalExtensions.GetTenantId()- Get tenant ID from claimsClaimsPrincipalExtensions.IsInTenant()- Check tenant access
| 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 was computed. 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 was computed. 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. |
-
net8.0
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 8.0.0)
- Microsoft.Extensions.Logging (>= 8.0.0)
- System.IdentityModel.Tokens.Jwt (>= 7.0.3)
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 | 88 | 12/30/2025 |
Initial release. See README for details.