DotNetAuthCore 1.0.0

dotnet add package DotNetAuthCore --version 1.0.0
                    
NuGet\Install-Package DotNetAuthCore -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DotNetAuthCore" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DotNetAuthCore" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="DotNetAuthCore" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DotNetAuthCore --version 1.0.0
                    
#r "nuget: DotNetAuthCore, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package DotNetAuthCore@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DotNetAuthCore&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=DotNetAuthCore&version=1.0.0
                    
Install as a Cake Tool

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

  1. Use permission-based auth instead of roles for fine-grained control
  2. Validate JWT tokens using JwtHelper for consistency
  3. Check tenant access for all multi-tenant operations
  4. Use permission attributes on controllers and actions
  5. Verify permissions in code for dynamic authorization
  6. Handle token expiration gracefully with proper error messages
  7. Include tenant ID in JWT claims for multi-tenant apps

API Reference

  • JwtHelper - Centralized JWT token validation
  • JwtHelperOptions - Configuration for JWT validation
  • PermissionAuthorizeAttribute - Attribute for permission-based authorization
  • ClaimsPrincipalExtensions.HasPermission() - Check if user has permission
  • ClaimsPrincipalExtensions.GetTenantId() - Get tenant ID from claims
  • ClaimsPrincipalExtensions.IsInTenant() - Check tenant access
Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.