TamimahAuthCore 1.1.1
dotnet add package TamimahAuthCore --version 1.1.1
NuGet\Install-Package TamimahAuthCore -Version 1.1.1
<PackageReference Include="TamimahAuthCore" Version="1.1.1" />
<PackageVersion Include="TamimahAuthCore" Version="1.1.1" />
<PackageReference Include="TamimahAuthCore" />
paket add TamimahAuthCore --version 1.1.1
#r "nuget: TamimahAuthCore, 1.1.1"
#:package TamimahAuthCore@1.1.1
#addin nuget:?package=TamimahAuthCore&version=1.1.1
#tool nuget:?package=TamimahAuthCore&version=1.1.1
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-Tokenheader - 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:
- Client sends request with
Authorization: Bearer <token>header - Middleware validates the token (ignoring expiration for sliding expiration)
- On successful response (2xx), generates a new token with same claims
- Returns new token in
X-New-Tokenresponse header - 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 | Versions 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. |
-
net9.0
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 9.0.0)
- System.IdentityModel.Tokens.Jwt (>= 8.3.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
v1.1.1: Added IP address claim support and validation. Tokens can now include client IP address and optionally validate it on each request.