smallrye-jwt icon indicating copy to clipboard operation
smallrye-jwt copied to clipboard

add an extension point to customize evaluation time used in DefaultJWTTokenParser

Open erickloss opened this issue 2 years ago • 8 comments

Hello there,

Problem

I need to set / mock the actual evaluation time for creating and verifying a JWT.

Why

we use quarkus / smallrye JWT in our project. I want to write end-to-end tests for the application. One whole scenario is: handling expired JWT tokens.

Our gherkin test looks somehow like this:

Scenario: expired JWT cookies lead to 401 page
    Given now is "2023-12-13T12:00:00+01:00"
    And I am authenticated as admin
    When now is "2023-12-23T12:00:00+01:00"
    And I browse the URL "/admin/dashboard"
    Then the status code must be 401

My solution idea

I've looked up the code, and there is an extension point in jose that allows you to set a static evaluation time org.jose4j.jwt.consumer.JwtConsumerBuilder#setEvaluationTime That could be exposed in smallrye-jwt via configuration or CDI extension point.

What do you think? Is this somehow possible already and I just haven't found a doc for that?

Any help would be nice :) Thank you + cheers

erickloss avatar Dec 13 '23 17:12 erickloss

Hi @erickloss

Can you clarify please a bit more why the current time is not suitable ? Is it because you may have some test tokens with the expiry time already in the past or too far in the future for the test to wait till they expire ?

The evaluation time seems to be a test scope only property but one can easily generate test tokens with the JWT build api, setting the required expiry time...

sberyozkin avatar Dec 18 '23 12:12 sberyozkin

Hello @sberyozkin , sorry for the late response.

Can you clarify please a bit more why the current time is not suitable ?

Our End-To-End test setup uses a Playwright Browser to Test most of the application in "real-life" mode. In the example gherkin code above, And I am authenticated as admin does the following things:

  • open the login form in a browser
  • enter valid credentials
  • press enter to engage form submit
  • waiting for the response that contains the JWT cookie

Basically, I want to test the whole login-feature as if a real user would login via web form. That includes the creation of the JWT by the library. If possible, I want to have no lines of code in the productive logic, that are only executed during testing. In other words, I don't want to reimplement the logic that sets the expiration date by myself when creating the JWT, as it is already implemented in the library using the property smallrye.jwt.new-token.lifespan.

What I did to Fix this on my side:

  • for now, I use Mockito to replace the actual method call of NumericDate::now with the test state that is set via Gherkin Step Given now is ...
  • that happens for every request that is handled during the tests via @ServerRequestFilter
  • it does not work, if I use JUnits/Cucumbers @Before hooks, since mocking statically with Mockito only works for the current thread
public class JwtMockDateProvider {

    private static MockedStatic<NumericDate> mocked;

    @ServerRequestFilter(preMatching = true)
    public void pre(ContainerRequestContext context) {
        mocked = Mockito.mockStatic(NumericDate.class, CALLS_REAL_METHODS);
        mocked.when(NumericDate::now).thenAnswer(i -> {
            if (TestCurrentDateProvider.mockNow != null) {
                return NumericDate.fromSeconds(TestCurrentDateProvider.mockNow.toEpochSecond());
            } else {
                return i.callRealMethod();
            }
        });
    }

    @ServerResponseFilter
    public void post(ContainerResponseContext context) {
        mocked.close();
    }

}

I might also simply reimplement the expiration date by myself setting the .expiresAt(...) on the JWT builder in our controller logic. But that would prevent me from using the library code that is already implemented just for the sake of testability.

I hope that clarifies my use-case a bit better. If you have any further questions, please tell me.

What do you think? Cheers + Thanks for the discussion

erickloss avatar Feb 15 '24 13:02 erickloss

PS: there are lots of other test-cases where we assume an already authenticated JWT. For all those tests, we create a JWT programmatically in the test code. This only concerns test-cases where we actually test the creation of the JWT.

erickloss avatar Feb 15 '24 13:02 erickloss

PPS: All I've written above concerns the creation of the JWT.

Concerning the validation / authentication of the JWT, I see no way to test an expired JWT that is created via the library without:

  1. setting the expiration time to a very low value
  2. wait/sleep during test execution to let the JWT expire

Point 1. would be in the category "Configuring the prod application differently, only for sake of testability" Point 2. would be just wrong imho ;)

erickloss avatar Feb 15 '24 14:02 erickloss

Hi @erickloss Apologies for not responding earlier, a lot is going on, and I was not sure this issue was of critical nature.

Would you be open to creating a PR, to allow configuring this property ?

sberyozkin avatar Apr 08 '24 10:04 sberyozkin

Hi @sberyozkin , thanks for your response. I totally understand that you are busy, no worries mate.

Yes, I would love to contribute. Earliest possible will be next week.

A few questions in advance:

  1. since I need to set the mock now-time programmatically via gherkin step, it would be nice to have an extension point rather than a fix configuration. Or maybe both? What do you think of that?
  2. I will look into the code and think of good way to implement it. I think, I will create a prototype branch and let you review it to validate that I am on the right track. Is that ok for you?

Cheers

erickloss avatar Apr 09 '24 14:04 erickloss

@erickloss Hey, by the way, I was thinking about it, we have smallrye.jwt.time-to-live property, for example, if you set it to 10 (secs) then if more than 10 secs have passed since the time it was issued at, the token is invalid.

Can you clarify again please (without gherkin scenarios), how can the evaluation time help instead. Lets say you have a token whose expiry claim will keep it valid for another hour from now. And, you'd like to test that if the token has expired, 401 is returned, for the higher level Gherkin scenario to pass, right ?

If the evaluation time property were available, how would you know how to set it correctly for the test to pass ?

Can you imagine smallrye.jwt.time-to-live being an alternative ?

sberyozkin avatar Apr 15 '24 11:04 sberyozkin

@erickloss I'm not sure what kind of an extension point you have in mind, can you prototype some code to clarify it ?

sberyozkin avatar Apr 15 '24 11:04 sberyozkin