Skip to content

Latest commit

 

History

History
285 lines (214 loc) · 9.52 KB

File metadata and controls

285 lines (214 loc) · 9.52 KB

MiniJwt.Core

CI Publish

Release Release

NuGet NuGet downloads dotnet

Open issues Contributors License Last commit

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.

Documentation

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

Sample Applications

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

Requirements

  • .NET 8+

Installation

Via the .NET CLI (after the package is published to NuGet):

dotnet add package MiniJwt.Core

Usage

1) Define the options (e.g. appsettings.json)

{
  "MiniJwt": {
    "SecretKey": "a-very-long-secret-key-at-least-32-bytes-...",
    "Issuer": "MyApp",
    "Audience": "MyClient",
    "ExpirationMinutes": 60
  }
}

2) Register in DI (e.g. Program.cs for an ASP.NET Core app)

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.

3) Define a model with claims

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; }
}

4) Generate a token

// 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 });
    }
}

5) Validate a token

var principal = _jwt.ValidateToken(token);
if (principal == null)
{
    // Invalid token
}
else
{
    // Valid token, access claims via principal.Claims
}

6) Validate and deserialize to an object

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
}

Unit test examples

If you create a service instance manually in a test, you have two options:

Option 1: Use AddMiniJwt (Recommended)

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>();

Option 2: Manual Instantiation

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
);

Testing with TimeProvider

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));

Debugging tips

  • If GenerateToken returns null, check the length of the SecretKey. It must be at least 32 bytes (for HS256).
  • For validation errors, use ValidateToken and enable logs to see exceptions captured by the service.
  • When publishing via CI (GitHub Actions), use vMAJOR.MINOR.PATCH tags to trigger package creation and to set the package version.

Security

  • 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.

Quick FAQ

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.

Contributing

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.

Support

If you encounter issues:

  1. Check the FAQ
  2. Review the sample applications
  3. Search existing issues
  4. Open a new issue with:
    • .NET version used
    • Minimal reproduction code
    • Logs/stacktraces

License

This project is licensed under the MIT License - see the LICENSE file for details.


Made with ❤️ by jeanlrnt