Skip to content

Jwt Authentication

Introduction

To facilitate user usage, we provide the functionality to manage Jwt keys, which can automatically generate keys and inject them into JwtBearerOptions. We offer various key storage methods such as InMemoryJwtSettingStore, FileJwtSettingStore, RedisJwtSettingStore, and DbContextJwtSettingStore, allowing users to choose the appropriate method based on their needs.

How to Use

Add package references:

# InMemory storage, File storage
dotnet add package NetCorePal.Extensions.Jwt   

# Redis storage
dotnet add package NetCorePal.Extensions.Jwt.StackExchangeRedis

# EntityFrameworkCore storage
dotnet add package NetCorePal.Extensions.Jwt.EntityFrameworkCore

Add the following configuration in your startup code (a minimal JWT setup):

using Microsoft.AspNetCore.Authentication.JwtBearer;

// Configure JWT authentication (validation parameters will be updated dynamically by the background service)
builder.Services
    .AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer();

// Register NetCorePal Jwt and choose a key store (example: in-memory)
builder.Services.AddNetCorePalJwt()
    .AddInMemoryStore();

If you need file storage for keys:

using Microsoft.AspNetCore.Authentication.JwtBearer;

builder.Services
    .AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer();

builder.Services.AddNetCorePalJwt()
    .AddFileStore("jwtsetting-filename.json"); // Use file storage for keys

Use Redis for key storage:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using StackExchange.Redis;

builder.Services
    .AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer();

// Add Redis connection
builder.Services.AddSingleton<IConnectionMultiplexer>(
    _ => ConnectionMultiplexer.Connect(builder.Configuration.GetConnectionString("Redis")!));

builder.Services.AddNetCorePalJwt()
    .AddRedisStore(); // Use Redis for key storage

To use EntityFrameworkCore for key storage, add the JwtSetting entity to your MyDbContext:

public class MyDbContext : DbContext, IJwtSettingDbContext
{
    public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
    {
    }

    public DbSet<JwtSetting> JwtSettings => Set<JwtSetting>();
}

Configure authentication and storage:

using Microsoft.AspNetCore.Authentication.JwtBearer;

builder.Services
    .AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer();

builder.Services.AddDbContext<MyDbContext>(options =>
{
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
});

builder.Services.AddNetCorePalJwt()
    .AddEntityFrameworkCoreStore<MyDbContext>(); // Use EntityFrameworkCore for key storage

Key Rotation

Key rotation (JwtKeyRotationService) is registered automatically when you call AddNetCorePalJwt() and is executed periodically by the background service (JwtHostedService).

To enable and customize rotation, configure JwtOptions via AddNetCorePalJwt:

builder.Services.AddNetCorePalJwt(options =>
{
    options.AutomaticRotationEnabled = true;                  // Enable automatic rotation
    options.KeyLifetime = TimeSpan.FromDays(30);              // Key validity (applies only when AutomaticRotationEnabled = true)
    options.RotationCheckInterval = TimeSpan.FromHours(1);    // Rotation check interval
    options.ExpiredKeyRetentionPeriod = TimeSpan.FromDays(30);// Keep expired keys to validate existing tokens
    options.MaxActiveKeys = 2;                                // Maximum number of active keys to keep
})
.AddInMemoryStore();

Note: When AutomaticRotationEnabled is false, newly generated keys are assigned a very long lifetime (100 years) and KeyLifetime is ignored.

Note: For single-instance scenarios, AddNetCorePalJwt() defaults to an in-memory lock for synchronization. For multi-instance/distributed deployments, configure a distributed lock (e.g., Redis) to avoid concurrent rotation conflicts:

// Requires NetCorePal.Extensions.DistributedLocks.Redis package
// and a registered IConnectionMultiplexer
builder.Services.AddRedisLocks(); // or AddRedisLocks(connectionMultiplexer)

Data Protection

Use ASP.NET Core DataProtection to protect stored JWT keys:

builder.Services.AddNetCorePalJwt()
    .UseDataProtection() // Enable encrypted key storage (call BEFORE selecting a store)
    .AddFileStore("jwtsetting-filename.json");

Important: Call UseDataProtection before selecting a store (e.g., AddInMemoryStore, AddFileStore, AddRedisStore, AddEntityFrameworkCoreStore). The DataProtection wrapper decorates the next IJwtSettingStore registration; if called after a store is already registered, encryption will not be applied to that store.

DataProtection automatically encrypts stored private key data, ensuring security of keys in files, databases, or Redis.

Generate JwtToken

In an endpoint, you can use the IJwtProvider interface to generate a JwtToken:

public class JwtLoginEndpoint : Endpoint<JwtLoginRequest, ResponseData<string>>
{
    public override void Configure()
    {
        Post("/jwtlogin");
        AllowAnonymous();
    }

    public override async Task HandleAsync(JwtLoginRequest req, CancellationToken ct)
    {
        var provider = Resolve<IJwtProvider>();
        var claims = new[]
        {
            new Claim("uid", "111"),
            new Claim("type", "client"),
            new Claim("email", "abc@efg.com"),
        };
        var jwt = await provider.GenerateJwtToken(new JwtData("issuer-x", "audience-y",
            claims,
            DateTime.Now,
            DateTime.Now.AddMinutes(1)));
        await SendAsync(jwt.AsResponseData(), cancellation: ct);
    }
}

public record JwtLoginRequest(string Name);