How-to guide

Test JWT authentication

Trax.Api.Auth.Jwt.Testing runs an identity provider in the test process, so a test drives the host's real JWT validation without a network dependency. Reference it, and for Cognito tokens Trax.Api.Auth.Jwt.Cognito.Issuer, from the test project only.

Point the host at a test JWKS server

Start a TestJwksServer, register the scheme against its issuer, and allow HTTP metadata, since the server serves plain HTTP. Then send a minted token:

using System.Net.Http.Headers;
using Trax.Api.Auth.Jwt;
using Trax.Api.Auth.Jwt.Testing;
 
await using var jwks = await TestJwksServer.StartAsync();
 
services.AddTraxJwtAuth(jwt => jwt
    .UseAuthority(jwks.Issuer, "test-aud")
    .AllowHttpMetadata());
 
var token = jwks.CreateIssuer("test-aud").Mint(b => b.WithSubject("alice").WithRole("admin"));
 
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

Give each test its own server: each binds its own port and generates its own keys.

Prove the negative paths

A token the host must refuse is minted the same way, with one thing wrong.

Signed with a key the server does not publish:

using System.Security.Cryptography;
using Microsoft.IdentityModel.Tokens;
using Trax.Api.Auth.Jwt.Testing;
 
await using var jwks = await TestJwksServer.StartAsync();
 
using var rogueKey = RSA.Create(2048);
var rogueCredentials = new SigningCredentials(new RsaSecurityKey(rogueKey), SecurityAlgorithms.RsaSha256);
var forged = new TestTokenIssuer(jwks.Issuer, "test-aud", rogueCredentials).Mint(b => b.WithSubject("intruder"));
// expect 401

Expired, or for another audience:

using Trax.Api.Auth.Jwt.Testing;
 
await using var jwks = await TestJwksServer.StartAsync();
var issuer = jwks.CreateIssuer("test-aud");
 
var expired = issuer.Mint(b => b.WithSubject("alice").WithExpires(DateTime.UtcNow.AddMinutes(-5)));
var wrongAudience = issuer.Mint(b => b.WithSubject("alice").WithAudience("another-api"));
// expect 401 for both

Keep the expiry well past the host's clock skew, five minutes unless WithClockSkew changed it.

Rotate keys under a running host

Publish a new key with AddSigningKey() and mint with it; the bearer handler fetches the JWKS again on the unknown key id and accepts the token without a restart. Remove the old key with RemoveSigningKey(kid) to check that its tokens stop validating. The calls are on TestJwksServer.

Test with Cognito-shaped tokens

Pair the server with a CognitoTokenIssuer and validate through UseCognito, pointed at the test server instead of the real pool:

using Trax.Api.Auth.Jwt;
using Trax.Api.Auth.Jwt.Cognito;
using Trax.Api.Auth.Jwt.Cognito.Issuer;
using Trax.Api.Auth.Jwt.Testing;
 
await using var server = await TestJwksServer.StartAsync();
var issuer = server.CreateCognitoIssuer();
 
services.AddTraxJwtAuth<CognitoJwtPrincipalResolver>(jwt =>
{
    jwt.UseCognito("us-east-1", "us-east-1_TestPool", "test-client").AllowHttpMetadata();
    jwt.CustomizeBearerOptions(opts => opts.Authority = server.Issuer);
});
 
var idToken = issuer.MintIdToken(new CognitoIdTokenRequest
{
    Sub = Guid.NewGuid(),
    ClientId = "test-client",
    Lifetime = TimeSpan.FromHours(1),
    Email = "alice@example.com",
    EmailVerified = true,
    Groups = ["admin"],
    Identities = [new FederatedIdentity("108222222222", "Google", "Google", Primary: true, DateCreated: DateTimeOffset.UtcNow)],
});

The resolved principal has PrincipalType cognito, Roles from cognito:groups, and Claims["identity_provider"] Google.

SDK Reference

TestJwksServer | TestTokenIssuer | CognitoTokenIssuer | AddTraxJwtAuth | UseCognito