JWTSimple 2.0.1

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

JWTSimple 🔑

JWTSimple is a lightweight authentication and authorization library for .NET 8+ applications.

Features

  • JWT authentication with issuer, audience, and token lifetime support
  • Refresh token generation and validation
  • Secure password hashing
  • Simple dependency injection (DI) registration
  • Support for custom user identifier types (e.g., Guid, int)
  • Flexible identity claim — use email, username, or any custom value
  • Fully generic refresh token model with typed Id and UserId

🚀 Installation

dotnet add package JWTSimple --version 2.0.1

Quick Start

1. Configure JWT

using JWTSimple.Extensions;
using JWTSimple.Models;

var builder = WebApplication.CreateBuilder(args);

// 1. Configure JWT settings
var jwtOptions = new JwtOptions
{
    SecretKey = Environment.GetEnvironmentVariable("JWT_SECRET")
        ?? "super-secret-key-that-must-be-very-long-32-characters!",
    Issuer = Environment.GetEnvironmentVariable("JWT_ISSUER") ?? "MyJWTSimple",
    Audience = Environment.GetEnvironmentVariable("JWT_AUDIENCE") ?? "MyApps",
    ExpiryInMinutes = 15 // Short-lived access token
};

// 2. Register JWTSimple dependencies (specify your user ID type)
builder.Services.AddCustomAuth<Guid>(jwtOptions);

builder.Services.AddControllers();

var app = builder.Build();

// 3. Enable authentication and authorization middleware
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();
app.Run();

2. Implement the User Model

using System.Security.Claims;
using JWTSimple.Models;

public class AppUser : IAuthUser<Guid>
{
    public Guid Id { get; set; }
    public string Email { get; set; } = string.Empty;
    public string Username { get; set; } = string.Empty;
    public string PasswordHash { get; set; } = string.Empty;
    public string Role { get; set; } = "User";

    // Optional: custom claims embedded into the JWT
    public IEnumerable<Claim> CustomClaims => new List<Claim>
    {
        new Claim(ClaimTypes.Role, Role)
    };

    // Refresh tokens — specify the same TId as in IAuthUser<TId>
    public List<RefreshToken<Guid>> RefreshTokens { get; set; } = new();
}

Note: IAuthUser<TId> no longer requires an Identity property. You decide what value goes into the name claim when calling GenerateToken.

3. Configure EF Core

using Microsoft.EntityFrameworkCore;

public class AppDbContext : DbContext
{
    public DbSet<AppUser> Users { get; set; }
    public DbSet<RefreshToken<Guid>> RefreshTokens { get; set; }

    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<AppUser>(entity =>
        {
            entity.HasKey(e => e.Id);
            entity.HasIndex(e => e.Email).IsUnique();
            entity.HasIndex(e => e.Username).IsUnique();

            entity.HasMany(u => u.RefreshTokens)
                .WithOne()
                .HasForeignKey(e => e.UserId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        modelBuilder.Entity<RefreshToken<Guid>>(entity =>
        {
            entity.HasKey(e => e.Id);
            entity.HasIndex(e => e.Token).IsUnique();
        });
    }
}

Note: Use RefreshToken<int> if your user ID type is int, or RefreshToken<Guid> for Guid. Both Id and UserId will use the same type.

4. Authentication Example

using System.Security.Claims;
using Microsoft.AspNetCore.Mvc;
using JWTSimple.Interfaces;
using JWTSimple.Models;

[ApiController]
[Route("api/auth")]
public class AuthController : ControllerBase
{
    private readonly IPasswordHasher _passwordHasher;
    private readonly IJwtTokenService<Guid> _jwtService;
    private readonly JwtOptions _jwtOptions;

    private static readonly List<AppUser> MockUserDatabase = new();

    public AuthController(
        IPasswordHasher passwordHasher,
        IJwtTokenService<Guid> jwtService,
        JwtOptions jwtOptions)
    {
        _passwordHasher = passwordHasher;
        _jwtService = jwtService;
        _jwtOptions = jwtOptions;
    }

    [HttpPost("login")]
    public IActionResult Login([FromBody] LoginRequest request)
    {
        var user = MockUserDatabase.FirstOrDefault(u => u.Email == request.Email);
        if (user == null)
            return Unauthorized("Invalid email or password");

        var isPasswordValid = _passwordHasher.VerifyPassword(request.Password, user.PasswordHash);
        if (!isPasswordValid)
            return Unauthorized("Invalid email or password");

        var accessToken = _jwtService.GenerateToken(user, _jwtOptions, user.Email);
        var refreshTokenValue = _jwtService.GenerateRefreshToken();

        var refreshToken = new RefreshToken<Guid>
        {
            Id = Guid.NewGuid(),
            Token = refreshTokenValue,
            ExpiresAt = DateTime.UtcNow.AddDays(30),
            UserId = user.Id
        };
        user.RefreshTokens.Add(refreshToken);

        return Ok(new AuthResponse(accessToken, refreshTokenValue));
    }

    [HttpPost("login-by-username")]
    public IActionResult LoginByUsername([FromBody] UsernameLoginRequest request)
    {
        var user = MockUserDatabase.FirstOrDefault(u => u.Username == request.Username);
        if (user == null)
            return Unauthorized("Invalid username or password");

        var isPasswordValid = _passwordHasher.VerifyPassword(request.Password, user.PasswordHash);
        if (!isPasswordValid)
            return Unauthorized("Invalid username or password");

        var accessToken = _jwtService.GenerateToken(user, _jwtOptions, user.Username);
        var refreshTokenValue = _jwtService.GenerateRefreshToken();

        var refreshToken = new RefreshToken<Guid>
        {
            Id = Guid.NewGuid(),
            Token = refreshTokenValue,
            ExpiresAt = DateTime.UtcNow.AddDays(30),
            UserId = user.Id
        };
        user.RefreshTokens.Add(refreshToken);

        return Ok(new AuthResponse(accessToken, refreshTokenValue));
    }

    [HttpPost("refresh")]
    public IActionResult Refresh([FromBody] RefreshRequest request)
    {
        try
        {
            var principal = _jwtService.GetPrincipalFromExpiredToken(request.AccessToken, _jwtOptions.SecretKey);
            var userIdString = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;

            if (string.IsNullOrEmpty(userIdString) || !Guid.TryParse(userIdString, out var userId))
                return Unauthorized("Invalid token");

            var user = MockUserDatabase.FirstOrDefault(u => u.Id == userId);
            if (user == null)
                return Unauthorized("User not found");

            var savedToken = user.RefreshTokens.FirstOrDefault(t => t.Token == request.RefreshToken);
            if (savedToken == null || savedToken.IsExpired)
                return Unauthorized("Invalid or expired refresh token");

            var newAccessToken = _jwtService.GenerateToken(user, _jwtOptions, user.Email);
            var newRefreshTokenValue = _jwtService.GenerateRefreshToken();

            user.RefreshTokens.Remove(savedToken);
            user.RefreshTokens.Add(new RefreshToken<Guid>
            {
                Id = Guid.NewGuid(),
                Token = newRefreshTokenValue,
                ExpiresAt = DateTime.UtcNow.AddDays(30),
                UserId = user.Id
            });

            return Ok(new AuthResponse(newAccessToken, newRefreshTokenValue));
        }
        catch
        {
            return Unauthorized("Failed to refresh token");
        }
    }
}

// DTO models
public record LoginRequest(string Email, string Password);
public record UsernameLoginRequest(string Username, string Password);
public record RefreshRequest(string AccessToken, string RefreshToken);
public record AuthResponse(string AccessToken, string RefreshToken);

Breaking Changes in v2.0

v1.x v2.0
IAuthUser<TId> required Identity property Identity removed from interface
GenerateToken(user, options) GenerateToken(user, options, identity)
RefreshToken without Id RefreshToken<TId> with fully typed Id and UserId
IEmailAuthenticatable, IUsernameAuthenticatable, IPhoneAuthenticatable Removed — no longer needed
RefreshToken.AssociatedAccessTokenId Removed — simplified model

Migration:

// Before (v1.x)
public class User : IAuthUser<Guid>
{
    public Guid Id { get; set; }
    public string Identity { get; set; } = null!;
    public string PasswordHash { get; set; } = null!;
    public List<RefreshToken> RefreshTokens { get; set; } = new();
}

var token = _jwtService.GenerateToken(user, options);

// After (v2.0)
public class User : IAuthUser<Guid>
{
    public Guid Id { get; set; }
    public string Email { get; set; } = null!;
    public string PasswordHash { get; set; } = null!;
    public List<RefreshToken<Guid>> RefreshTokens { get; set; } = new();
}

var token = _jwtService.GenerateToken(user, options, user.Email);

var refreshToken = new RefreshToken<Guid>
{
    Id = Guid.NewGuid(),
    Token = tokenValue,
    ExpiresAt = DateTime.UtcNow.AddDays(30),
    UserId = user.Id
};

Configuration

The library uses the following JwtOptions class:

public class JwtOptions
{
    public string SecretKey { get; set; } = string.Empty;
    public string Issuer { get; set; } = string.Empty;
    public string Audience { get; set; } = string.Empty;
    public int ExpiryInMinutes { get; set; } = 30;
}

Important:

  • SecretKey must be at least 32 characters long.

Architecture & Core Components

  • IPasswordHasher — password hashing and verification
  • IJwtTokenService<TId> — JWT and refresh token generation
  • JwtOptions — JWT configuration
  • RefreshToken<TId> — fully generic refresh token model with typed Id and UserId
  • IAuthUser<TId> — minimal user contract (Id, PasswordHash, optional CustomClaims)

License

This project is licensed under the MIT License.

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
2.0.1 125 8/16/2026
2.0.0 114 8/15/2026
1.0.1 107 7/15/2026
1.0.0 113 7/15/2026