TamimahAuthCore 1.1.1

dotnet add package TamimahAuthCore --version 1.1.1
                    
NuGet\Install-Package TamimahAuthCore -Version 1.1.1
                    
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="TamimahAuthCore" Version="1.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TamimahAuthCore" Version="1.1.1" />
                    
Directory.Packages.props
<PackageReference Include="TamimahAuthCore" />
                    
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 TamimahAuthCore --version 1.1.1
                    
#r "nuget: TamimahAuthCore, 1.1.1"
                    
#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 TamimahAuthCore@1.1.1
                    
#: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=TamimahAuthCore&version=1.1.1
                    
Install as a Cake Addin
#tool nuget:?package=TamimahAuthCore&version=1.1.1
                    
Install as a Cake Tool

TamimahAuthCore

A standalone JWT authentication and authorization library for ASP.NET Core Web APIs targeting .NET 9, with refresh token support and claims-based access control.

Features

  • JWT Token Generation & Validation - Secure token creation with configurable expiration
  • Multiple Signing Algorithms - Support for both HS256 (HMAC) and RS256 (RSA) algorithms
  • Refresh Token Support - Built-in refresh token management with rotation
  • Token Refresh Middleware - Automatic sliding expiration with X-New-Token header
  • Custom Authorization Policies - Permission-based, role-based, and claims-based authorization
  • Claims Builder - Fluent API for building claims collections
  • Extension Methods - Easy access to common claims from ClaimsPrincipal
  • Easy Integration - Simple service registration with dependency injection

Installation

dotnet add package TamimahAuthCore

Quick Start

1. Configure Services

// Program.cs
using Tamimah.Extensions;

var builder = WebApplication.CreateBuilder(args);

// Add Tamimah authentication and authorization
builder.Services.AddTamimah(
    auth => 
    {
        auth.SecretKey = "your-secret-key-at-least-32-characters-long!";
        auth.Issuer = "your-app";
        auth.Audience = "your-api";
        auth.AccessTokenExpirationMinutes = 15;
        auth.RefreshTokenExpirationDays = 7;
    },
    authz =>
    {
        authz.AddPolicy("AdminOnly", policy => 
            policy.RequireRole("Admin"));
        authz.AddPolicy("CanEditUsers", policy => 
            policy.RequirePermission("users:edit"));
    }
);

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.Run();

2. Generate Tokens

using Tamimah.Claims;
using Tamimah.Services;

public class AuthController : ControllerBase
{
    private readonly IJwtTokenService _jwtTokenService;
    private readonly IRefreshTokenService _refreshTokenService;

    public AuthController(
        IJwtTokenService jwtTokenService,
        IRefreshTokenService refreshTokenService)
    {
        _jwtTokenService = jwtTokenService;
        _refreshTokenService = refreshTokenService;
    }

    [HttpPost("login")]
    public async Task<IActionResult> Login([FromBody] LoginRequest request)
    {
        // Validate credentials (your logic here)
        var userId = "user123";

        // Build claims using the fluent builder
        var claims = ClaimsBuilder.Create()
            .AddUserId(userId)
            .AddEmail("user@example.com")
            .AddRoles("Admin", "User")
            .AddPermissions("users:read", "users:write")
            .AddTenantId("tenant1")
            .Build();

        // Generate tokens
        var accessToken = _jwtTokenService.GenerateAccessToken(claims);
        var refreshToken = await _refreshTokenService.CreateRefreshTokenAsync(userId);

        return Ok(new
        {
            accessToken = accessToken.Token,
            expiresAt = accessToken.ExpiresAt,
            refreshToken = refreshToken.Token
        });
    }

    [HttpPost("refresh")]
    public async Task<IActionResult> Refresh([FromBody] RefreshRequest request)
    {
        // Validate refresh token
        var isValid = await _refreshTokenService.ValidateRefreshTokenAsync(request.RefreshToken);
        if (!isValid)
        {
            return Unauthorized("Invalid refresh token");
        }

        // Get the old token to extract user info
        var oldToken = await _refreshTokenService.GetRefreshTokenAsync(request.RefreshToken);
        
        // Rotate refresh token
        var newRefreshToken = await _refreshTokenService.RotateRefreshTokenAsync(request.RefreshToken);
        
        // Generate new access token
        var accessToken = _jwtTokenService.GenerateAccessToken(oldToken!.UserId);

        return Ok(new
        {
            accessToken = accessToken.Token,
            refreshToken = newRefreshToken!.Token
        });
    }
}

3. Protect Endpoints

using Microsoft.AspNetCore.Authorization;
using Tamimah.Authorization.Policies;
using Tamimah.Claims;

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    // Require authentication
    [Authorize]
    [HttpGet]
    public IActionResult GetUsers()
    {
        var userId = User.GetUserId();
        var permissions = User.GetPermissions();
        return Ok(new { userId, permissions });
    }

    // Require specific role
    [Authorize(Roles = "Admin")]
    [HttpDelete("{id}")]
    public IActionResult DeleteUser(string id)
    {
        return Ok();
    }

    // Require specific permission
    [Authorize(Policy = "CanEditUsers")]
    [HttpPut("{id}")]
    public IActionResult UpdateUser(string id)
    {
        return Ok();
    }

    // Use built-in policy names
    [Authorize(Policy = PolicyNames.AdminOnly)]
    [HttpGet("admin")]
    public IActionResult AdminEndpoint()
    {
        return Ok();
    }
}

Configuration

TamimahOptions

Property Type Default Description
Algorithm JwtAlgorithm HS256 Signing algorithm (HS256 or RS256)
SecretKey string - Secret key for HS256 (min 32 chars)
RsaPrivateKey string - RSA private key in PEM format (for RS256)
RsaPublicKey string - RSA public key in PEM format (for RS256)
Issuer string - Token issuer (iss claim)
Audience string - Token audience (aud claim)
AccessTokenExpirationMinutes int 15 Access token lifetime
RefreshTokenExpirationDays int 7 Refresh token lifetime
ValidateIssuer bool true Validate issuer during token validation
ValidateAudience bool true Validate audience during token validation
ValidateLifetime bool true Validate token expiration
ValidateIssuerSigningKey bool true Validate signing key
ClockSkew TimeSpan 0 Clock skew tolerance
RequireHttps bool true Require HTTPS for auth endpoints
EnableTokenRefresh bool false Enable automatic token refresh middleware

Configuration from appsettings.json

HS256 (HMAC - Default):

{
  "Tamimah": {
    "Algorithm": "HS256",
    "SecretKey": "your-secret-key-at-least-32-characters-long!",
    "Issuer": "your-app",
    "Audience": "your-api",
    "AccessTokenExpirationMinutes": 15,
    "RefreshTokenExpirationDays": 7
  }
}

RS256 (RSA):

{
  "Tamimah": {
    "Algorithm": "RS256",
    "RsaPrivateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIE...\n-----END RSA PRIVATE KEY-----",
    "RsaPublicKey": "-----BEGIN RSA PUBLIC KEY-----\nMIIB...\n-----END RSA PUBLIC KEY-----",
    "Issuer": "your-app",
    "Audience": "your-api",
    "AccessTokenExpirationMinutes": 15,
    "RefreshTokenExpirationDays": 7
  }
}
builder.Services.AddTamimahAuthentication(
    builder.Configuration.GetSection("Tamimah")
);

RS256 (RSA) Algorithm

For asymmetric key signing, use RS256 with RSA key pairs:

builder.Services.AddTamimahAuthentication(options =>
{
    options.Algorithm = JwtAlgorithm.RS256;
    options.RsaPrivateKey = File.ReadAllText("private_key.pem");
    options.RsaPublicKey = File.ReadAllText("public_key.pem");
    options.Issuer = "your-app";
    options.Audience = "your-api";
});

Generate RSA keys:

# Generate private key
openssl genrsa -out private_key.pem 2048

# Extract public key
openssl rsa -in private_key.pem -pubout -out public_key.pem

RS256 is useful when:

  • You need to share the public key with other services for validation
  • You want to keep the signing key (private) separate from validation key (public)
  • You're implementing a distributed authentication system

Claims Builder

Build claims using the fluent API:

var claims = ClaimsBuilder.Create()
    .AddUserId("user123")
    .AddEmail("user@example.com")
    .AddUsername("johndoe")
    .AddFullName("John Doe")
    .AddName("John", "Doe")
    .AddRoles("Admin", "User")
    .AddPermissions("users:read", "users:write", "posts:read")
    .AddTenantId("tenant1")
    .AddOrganizationId("org1")
    .AddPhoneNumber("+1234567890")
    .AddEmailVerified(true)
    .AddScopes("read", "write")
    .AddJti()  // Adds unique token ID
    .AddIssuedAt()  // Adds issued at timestamp
    .AddSessionId()  // Adds session ID
    .AddCustomClaim("custom_key", "custom_value")
    .Build();

Claims Principal Extensions

Access claims easily from HttpContext.User:

// Get user information
var userId = User.GetUserId();
var email = User.GetEmail();
var username = User.GetUsername();
var fullName = User.GetFullName();
var firstName = User.GetFirstName();
var lastName = User.GetLastName();

// Get roles and permissions
var roles = User.GetRoles();
var permissions = User.GetPermissions();

// Check permissions
bool canRead = User.HasPermission("users:read");
bool canReadOrWrite = User.HasAnyPermission("users:read", "users:write");
bool canReadAndWrite = User.HasAllPermissions("users:read", "users:write");

// Check roles
bool isAdmin = User.IsInRole("Admin");
bool isAdminOrMod = User.HasAnyRole("Admin", "Moderator");
bool isAdminAndUser = User.HasAllRoles("Admin", "User");

// Multi-tenant support
var tenantId = User.GetTenantId();
var orgId = User.GetOrganizationId();

// Other claims
var sessionId = User.GetSessionId();
var scopes = User.GetScopes();
bool hasScope = User.HasScope("read");
bool isEmailVerified = User.IsEmailVerified();

Authorization Policies

Built-in Policies

using Tamimah.Authorization.Policies;

// Available policy names
PolicyNames.Authenticated  // Requires authenticated user
PolicyNames.AdminOnly      // Requires Admin role
PolicyNames.EmailVerified  // Requires verified email

Custom Policies

builder.Services.AddTamimahAuthorization(options =>
{
    // Permission-based
    options.AddPolicy("CanReadUsers", policy => 
        policy.RequirePermission("users:read"));
    
    options.AddPolicy("CanManageUsers", policy => 
        policy.RequireAnyPermission("users:read", "users:write", "users:delete"));
    
    options.AddPolicy("FullUserAccess", policy => 
        policy.RequireAllPermissions("users:read", "users:write", "users:delete"));

    // Role-based
    options.AddPolicy("AdminOrModerator", policy => 
        policy.RequireAnyRole("Admin", "Moderator"));
    
    options.AddPolicy("SuperUser", policy => 
        policy.RequireAllRoles("Admin", "PowerUser"));

    // Claims-based
    options.AddPolicy("VerifiedEmail", policy => 
        policy.RequireEmailVerified());
    
    options.AddPolicy("SpecificTenant", policy => 
        policy.RequireTenant("tenant1"));
    
    options.AddPolicy("HasScope", policy => 
        policy.RequireScope("api:full"));

    // Combined policies
    options.AddPolicy("AdminWithPermission", policy => 
        policy.RequireRole("Admin")
              .RequirePermission("users:manage"));
});

Custom Refresh Token Storage

The default RefreshTokenService uses in-memory storage. For production, implement IRefreshTokenService with persistent storage:

public class DatabaseRefreshTokenService : IRefreshTokenService
{
    private readonly AppDbContext _context;

    public DatabaseRefreshTokenService(AppDbContext context)
    {
        _context = context;
    }

    public async Task<RefreshToken> CreateRefreshTokenAsync(string userId, string? ipAddress = null)
    {
        var token = RefreshToken.Create(userId, 7, ipAddress);
        _context.RefreshTokens.Add(token);
        await _context.SaveChangesAsync();
        return token;
    }

    // Implement other methods...
}

// Register custom service
builder.Services.AddCustomRefreshTokenService<DatabaseRefreshTokenService>();

Claim Types

Custom claim types defined in TamimahClaimTypes:

Constant Value Description
Permission permission User permissions
UserId uid User identifier
Email email Email address
Username username Username
FullName name Full name
FirstName given_name First name
LastName family_name Last name
TenantId tenant_id Tenant identifier
OrganizationId org_id Organization identifier
EmailVerified email_verified Email verification status
SessionId sid Session identifier
Scope scope OAuth scopes

Token Validation

// Validate a token
var principal = _jwtTokenService.ValidateToken(token);
if (principal != null)
{
    // Token is valid
    var userId = principal.GetUserId();
}

// Validate ignoring expiration (useful for refresh)
var principal = _jwtTokenService.ValidateTokenIgnoringExpiration(expiredToken);

// Check if token is expired
bool isExpired = _jwtTokenService.IsTokenExpired(token);

// Get token expiration
DateTime? expiration = _jwtTokenService.GetTokenExpiration(token);

// Get specific claim
string? userId = _jwtTokenService.GetClaimValue(token, JwtRegisteredClaimNames.Sub);

// Get all claims
var claims = _jwtTokenService.GetClaims(token);

Token Refresh

Manual Token Refresh

Refresh tokens programmatically while preserving claims:

// Refresh a valid token (returns null if token is invalid or expired)
var newToken = _jwtTokenService.RefreshToken(existingToken);

// Refresh even expired tokens (useful for sliding expiration)
var newToken = _jwtTokenService.RefreshTokenIgnoringExpiration(expiredToken);

// Add additional claims during refresh
var additionalClaims = new[] { new Claim(ClaimTypes.Role, "NewRole") };
var newToken = _jwtTokenService.RefreshToken(existingToken, additionalClaims);

Automatic Token Refresh Middleware

Enable automatic token refresh on every successful response. The middleware validates the incoming token and returns a fresh token in the X-New-Token response header:

var app = builder.Build();

// Add token refresh middleware BEFORE authentication
app.UseTamimahTokenRefresh();

app.UseAuthentication();
app.UseAuthorization();

app.Run();

How it works:

  1. Client sends request with Authorization: Bearer <token> header
  2. Middleware validates the token (ignoring expiration for sliding expiration)
  3. On successful response (2xx), generates a new token with same claims
  4. Returns new token in X-New-Token response header
  5. Client can use the new token for subsequent requests

Client-side handling (JavaScript example):

async function fetchWithTokenRefresh(url, options = {}) {
    const response = await fetch(url, {
        ...options,
        headers: {
            ...options.headers,
            'Authorization': `Bearer ${getToken()}`
        }
    });

    // Check for refreshed token
    const newToken = response.headers.get('X-New-Token');
    if (newToken) {
        saveToken(newToken); // Update stored token
    }

    return response;
}

Middleware

JWT Middleware

Optional middleware for custom token handling scenarios:

app.UseTamimahJwt(); // Validates token and sets HttpContext.User

Token Refresh Middleware

Automatic token refresh with sliding expiration:

app.UseTamimahTokenRefresh(); // Returns refreshed token in X-New-Token header

API Reference

IJwtTokenService

Method Description
GenerateAccessToken(claims) Generate token with specified claims
GenerateAccessToken(userId, additionalClaims?) Generate token for user ID
ValidateToken(token) Validate token and return ClaimsPrincipal
ValidateTokenIgnoringExpiration(token) Validate token ignoring expiration
RefreshToken(token, additionalClaims?) Refresh a valid token
RefreshTokenIgnoringExpiration(token, additionalClaims?) Refresh token ignoring expiration
GetClaimValue(token, claimType) Get specific claim value
GetClaims(token) Get all claims from token
IsTokenExpired(token) Check if token is expired
GetTokenExpiration(token) Get token expiration date

IRefreshTokenService

Method Description
CreateRefreshTokenAsync(userId, ipAddress?) Create new refresh token
ValidateRefreshTokenAsync(token) Validate refresh token
GetRefreshTokenAsync(token) Get refresh token details
RotateRefreshTokenAsync(token, ipAddress?) Rotate refresh token
RevokeRefreshTokenAsync(token, reason?) Revoke refresh token
RevokeAllUserTokensAsync(userId, reason?) Revoke all user tokens

License

MIT License

Product Compatible and additional computed target framework versions.
.NET 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 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.1.1 95 1/21/2026
1.1.0 93 1/21/2026

v1.1.1: Added IP address claim support and validation. Tokens can now include client IP address and optionally validate it on each request.