MiniJwt.Core is a lightweight, minimal JWT library for .NET that provides a simple and efficient way to generate and validate JWT tokens using attributes on object properties to define claims. It's designed to be dependency-injection friendly, multi-target framework compatible, and easy to integrate.
Getting Started Guide - Installation and quick start
Configuration Guide - Detailed configuration options
Examples - Code examples and integration patterns
FAQ - Common questions and security best practices
The repository includes three runnable sample applications demonstrating different integration scenarios:
- ConsoleMinimal - Basic console app for token generation and validation
- ASPNetCoreAuth - Full ASP.NET Core web API with JWT authentication
- WorkerService - Background service example with periodic token generation
- .NET 8+
Via the .NET CLI (after the package is published to NuGet):
dotnet add package MiniJwt.Core{
"MiniJwt": {
"SecretKey": "a-very-long-secret-key-at-least-32-bytes-...",
"Issuer": "MyApp",
"Audience": "MyClient",
"ExpirationMinutes": 60
}
}using MiniJwt.Core.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Register MiniJwt with configuration from appsettings.json
builder.Services.AddMiniJwt(options =>
{
var config = builder.Configuration.GetSection("MiniJwt");
options.SecretKey = config["SecretKey"];
options.Issuer = config["Issuer"];
options.Audience = config["Audience"];
options.ExpirationMinutes = double.Parse(config["ExpirationMinutes"] ?? "60");
});
var app = builder.Build();Note: The AddMiniJwt extension method registers IMiniJwtService as a singleton with all required dependencies, including a private JwtSecurityTokenHandler instance that won't conflict with other JWT libraries in your application.
using MiniJwt.Core.Attributes;
public class UserJwtPayload
{
[MiniJwtClaim("id")]
public int Id { get; set; }
[MiniJwtClaim("email")]
public string? Email { get; set; }
[MiniJwtClaim("name")]
public string? Name { get; set; }
}// Example inside a controller or service where IMiniJwtService is injected
public class AuthController : ControllerBase
{
private readonly IMiniJwtService _jwt;
public AuthController(IMiniJwtService jwt)
{
_jwt = jwt;
}
public IActionResult Login()
{
var payload = new UserJwtPayload { Id = 1, Email = "test@example.com", Name = "Jean" };
var token = _jwt.GenerateToken(payload);
if (token == null) return StatusCode(500, "Failed to generate token");
return Ok(new { token });
}
}var principal = _jwt.ValidateToken(token);
if (principal == null)
{
// Invalid token
}
else
{
// Valid token, access claims via principal.Claims
}var user = _jwt.ValidateAndDeserialize<UserJwtPayload>(token);
if (user == null)
{
// Invalid token or missing claims
}
else
{
// user.Id, user.Email, user.Name are populated if present in the token
}If you create a service instance manually in a test, you have two options:
using Microsoft.Extensions.DependencyInjection;
using MiniJwt.Core.Extensions;
var services = new ServiceCollection();
services.AddMiniJwt(options =>
{
options.SecretKey = "IntegrationTestSecretKey_LongEnough_For_HS256_0123456789";
options.Issuer = "MiniJwt.Tests";
options.Audience = "MiniJwt.Tests.Client";
options.ExpirationMinutes = 60;
});
var serviceProvider = services.BuildServiceProvider();
var svc = serviceProvider.GetRequiredService<IMiniJwtService>();For direct instantiation without DI, provide all dependencies explicitly:
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using System.IdentityModel.Tokens.Jwt;
var options = Options.Create(new MiniJwtOptions
{
SecretKey = "IntegrationTestSecretKey_LongEnough_For_HS256_0123456789",
Issuer = "MiniJwt.Tests",
Audience = "MiniJwt.Tests.Client",
ExpirationMinutes = 60
});
var optionsMonitor = Options.CreateMonitor(options);
var tokenHandler = new JwtSecurityTokenHandler { MapInboundClaims = false };
var svc = new MiniJwtService(
optionsMonitor,
NullLogger<MiniJwtService>.Instance,
tokenHandler
);For testable time-dependent behavior, the library supports TimeProvider (built-in for .NET 8+ or via Microsoft.Bcl.TimeProvider for earlier versions). You can inject a FakeTimeProvider for deterministic testing:
using Microsoft.Extensions.Time.Testing;
using Microsoft.Extensions.Options;
using System.IdentityModel.Tokens.Jwt;
var fakeTimeProvider = new FakeTimeProvider();
fakeTimeProvider.SetUtcNow(new DateTimeOffset(2024, 1, 15, 10, 0, 0, TimeSpan.Zero));
var options = Options.Create(new MiniJwtOptions
{
SecretKey = "IntegrationTestSecretKey_LongEnough_For_HS256_0123456789",
Issuer = "MiniJwt.Tests",
Audience = "MiniJwt.Tests.Client",
ExpirationMinutes = 60
});
var svc = new MiniJwtService(
Options.CreateMonitor(options),
NullLogger<MiniJwtService>.Instance,
new JwtSecurityTokenHandler { MapInboundClaims = false },
fakeTimeProvider
);
// Generate token at the fixed time
var token = svc.GenerateToken(user);
// Advance time for further testing
fakeTimeProvider.Advance(TimeSpan.FromMinutes(5));- If
GenerateTokenreturnsnull, check the length of theSecretKey. It must be at least 32 bytes (for HS256). - For validation errors, use
ValidateTokenand enable logs to see exceptions captured by the service. - When publishing via CI (GitHub Actions), use
vMAJOR.MINOR.PATCHtags to trigger package creation and to set the package version.
- Never store the secret key in plain text in a public repository.
- Use a secrets manager (Azure Key Vault, GitHub Secrets, etc.) for your keys in CI/CD.
Q: How do I set the package version when packing?
A: You can use /p:PackageVersion=1.2.3 with dotnet pack or pack from a project that already has the Version set in the csproj.
Q: Why does ValidateAndDeserialize<T> require T to have a parameterless constructor?
A: The service creates an instance of T using the parameterless constructor and then assigns properties from the claims.
Contributions are welcome! Please:
- Follow the existing code style
- Add tests for new features
- Update documentation as needed
- Keep the library minimal and focused
See the examples documentation for development guidelines.
If you encounter issues:
- Check the FAQ
- Review the sample applications
- Search existing issues
- Open a new issue with:
- .NET version used
- Minimal reproduction code
- Logs/stacktraces
This project is licensed under the MIT License - see the LICENSE file for details.
Made with ❤️ by jeanlrnt