Home > Software engineering >  Do not validate JWT expiration in tests
Do not validate JWT expiration in tests

Time:11-06

I'm writing an integration test for my web service with JWT authentication. I'd like to test it with a token received from the real service. The problem is real tokens expire in 1 hour.

A possible way is to set options.TokenValidationParameters.ValidateLifetime inside AddJwtBearer of class Startup below. However, Startup class is a also a code to be tested, so I don't want to change or replace it for testing.

Is there a neat way to test all JWT validation logic except expiration?

My project code:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        services.AddAuthentication("JWT")
            .AddJwtBearer("JWT", options =>
            {
                options.Authority = "https://my-real-identity-server.com/";
                options.Audience = "...";
                // I don't want to disable lifetime validation in real life
                // options.TokenValidationParameters.ValidateLifetime = false;
            });
        
        // Other stuff
    }

    public void Configure(IApplicationBuilder app) => app
        .UseRouting()
        .UseAuthentication()
        .UseAuthorization()
        .UseEndpoints(endpoints => endpoints.MapControllers());
}

public class TestController : ControllerBase
{
    [Authorize]
    [HttpGet("/validate")]
    public string Get() => "success";
}

My test code:

public class HostBuilderTests
{
    private IHost testHost;
    private CancellationTokenSource cancel;
    private HttpClient client;

    [SetUp]
    public async Task ShouldReturnStatus()
    {
        testHost = Host.CreateDefaultBuilder()
            .ConfigureWebHostDefaults(webBuilder =>
                webBuilder
                    .UseStartup<Startup>()
                    .UseTestServer())
            .ConfigureServices(services => services
                .AddLogging(b => b.ClearProviders().AddNUnit()))
            .Build();

        cancel = new CancellationTokenSource(10000);

        var server = testHost.Services.GetRequiredService<IServer>()
            .ShouldBeOfType<TestServer>();
        await testHost.StartAsync(cancel.Token);
        client = server.CreateClient();
    }

    [TearDown]
    public async Task TearDown()
    {
        await testHost.StopAsync(cancel.Token);

        client.Dispose();
        testHost.Dispose();
        cancel.Dispose();
    }

    [Test]
    [TestCase("<<JWT token copied from the real service>>")]
    public async Task StatusShouldBeOk(string realToken)
    {
        client.DefaultRequestHeaders.Authorization =
            new AuthenticationHeaderValue("Bearer", realToken);

        using var response = await client.GetAsync("/validate", cancel.Token);
        response.StatusCode.ShouldBe(HttpStatusCode.OK);
    }
}

CodePudding user response:

Finally I found a simple way to manage options for authentication handlers:

An authentication scheme is a name which corresponds to:

  • An authentication handler.
  • Options for configuring that specific instance of the handler.

See MSDN.

So it suffices to post-configure JwtBearerOptions specifying the authentication scheme name "JWT" as options instance name in the test setup:

testHost = Host.CreateDefaultBuilder()
    // other setup
    .ConfigureServices(services => services
        .PostConfigure<JwtBearerOptions>("JWT", 
            op => op.TokenValidationParameters.ValidateLifetime = false)
        // other setup
    ).Build();

Also it is possible to pass null instead of "JWT" as it written in comments:

// Null name is used to configure all named options.
  • Related