Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add an extension point to customize evaluation time used in DefaultJWTTokenParser #757

Open
erickloss opened this issue Dec 13, 2023 · 8 comments

Comments

@erickloss
Copy link

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

@sberyozkin
Copy link
Contributor

sberyozkin commented Dec 18, 2023

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

@erickloss
Copy link
Author

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
Copy link
Author

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
Copy link
Author

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

@sberyozkin
Copy link
Contributor

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 ?

@erickloss
Copy link
Author

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

@sberyozkin
Copy link
Contributor

@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
Copy link
Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants