Requesting feed back on using pac4j 6.3.3 with dropwizard and keycloak

3 views
Skip to first unread message

Kusuma G

unread,
Apr 3, 2026, 1:26:36 AM (3 days ago) Apr 3
to Pac4j development mailing list
Hi ,
I am looking for some guidance in setting up and using pac4j (version 6.3.3) in a dropwizard (version 5.0.1) application with keycloak (version 26.x) as the IdentityProvider.

The application has to support multiple tenants and its APIs have to be used both in UI applications (calling the APIs in browser sessions) as well as other java applications (calling the APIs in code).

The dropwizard application is set up with the pac4j DefaultSecurityLogic.
The DefaultSecurityLogic is configured with:
1. A custom ClientFinder
    - The custom Client finder prepares and caches the KeycloakOidcConfiguration for each tenant.
    - Creates both a DirectBearerAuthClient and a KeycloakOidcClient for each tenant.
    - If a bearer token is present in the request it returns the DirectBearerAuthClient, else returns the KeycloakOidcClient.
2. The DirectBearerAuthClient is configured with the JwtAuthenticator using RSASignatureConfiguration with the tenant's publickey from keycloak.

The expectation is that if a user requests a protected resource from a browser, the KeycloakOidcConfiguration (IndirectClient) does the initial authentication.
Once a valid session is established subsequent requests for protected resources would include the bearer token and the security logic would switch to using the DirectBearerAuthClient (DirectClient).

In this scenario, I noticed that the requests coming in with bearer token (other than the initial /callback during oidc login) are authenticated without actually validating the bearer token.

So, the DefaultSecurityLogic has been extended and the "perform" function overridden to check the presence of the bearer token in the request.
Also observed that the BaseClient just logs warnings about expired tokens, so an extra check has been added to the custom DefaultSecurityLogic to make sure the credentials is not empty after validation and that it contains a valid UserProfile.

The JwtAuthenticator does not validate the claims, like exp, iss and aud, so that has been extended as well and customized to validate the exp, iss and aud claims.

Furthermore, these pac4j classes have also been extended and customized:
1. DefaultCallbackLogic: to handle state verification exceptions by redirecting to login again.
2. OidcAuthenticator: to read the CodeVerifier from auth request stored in session.
3. OidcCredentialsExtractor: to extract state value from auth request stored in session
4. OidcProfileCreator: to read the Nonce from auth request stored in session.
5. OidcRedirectionActionBuilder: to store login request details in the session store with a unique lookup key based on login request's state+nonce+verifier.
6. SavedRequestHandler: to save and restore the requested URL in the auth request stored in the session.
7. TenantAwareSessionStore extends JEESessionStore to add a tenant specific prefix to all keys stored in a tenant's session.

All of these customizations were made to support the following application usage scenarios:
1. A user accesses the application from multiple tabs in a browser for same tenant and login id.
2. A user accesses the application from multiple tabs in a browser for different tenants.
3. If the browser's session expires (30 minutes by default, based on the jetty server's JSESSION cookie), the user should be taken back to the login page.
4. If the login page has been inactive for 30 minutes and the user attempts to login it should reload the login page.
5. If an expired bearer token (created over 5min ago) is used to make a request from curl or any other app like postman/bruno, it should get a 401 - Unauthorized response.

Without some of these customizations especially the ones required for verification of nonce, code and state during OIDC login, the browser goes into a infinite login redirect loop.

1. Please let me know your thoughts on this approach.
2. Is there a different simpler/more efficient way to set up and configure pac4j in the dropwizard application?
3. The immediate concerns are usage of tampered or expired tokens in a browser or API session. Is the customization of DefaultSecurityLogic the only way to ensure tokens are validated for every request in the Indirect to Direct client switch scenario?

Looking forward to your thoughts and guidance.
Thank you!

Jérôme LELEU

unread,
Apr 3, 2026, 4:17:08 AM (2 days ago) Apr 3
to Kusuma G, Pac4j development mailing list
Hi,

Please use stack overflow with the pac4j tag for questions: https://www.pac4j.org/mailing-lists.html

The last version of dropwizard-pac4j v7.0.0 is based on pac4j v5.x, not pac4j v6.x and I haven't tested it with pac4j v6.

To give you some feedback:

** In this scenario, I noticed that the requests coming in with bearer token (other than the initial /callback during oidc login) are authenticated without actually validating the bearer token.
-> The OIDC login process has saved the authenticated user and the JWT authn checks if there is an already authenticated user before performing the authn. So after an OIDC login process, the JWT authn will never really be performed.
So it's a matter of session, do you re-use the one used for the OIDC login process to perform the bearer calls?
Otherwise, you could block that in pac4j with setLoadProfilesFromSession(false).

If I use this simple demo (Spring Boot MVC): https://github.com/pac4j/simple-spring-boot-pac4j-demos

I can add a DirectBearerAuthClient:

@Bean
public Config config() {
// configuration of the authentication via the OpenID Connect protocol
final var config = new OidcConfiguration()
.setDiscoveryURI("https://casserverpac4j.herokuapp.com/oidc/.well-known/openid-configuration")
.setClientId("myclient")
.setSecret("mysecret")
.setAllowUnsignedIdTokens(true);
final var oidcClient = new OidcClient(config);

final var bearerClient = new DirectBearerAuthClient(new JwtAuthenticator(new SecretSignatureConfiguration("whatever")));

final var clients = new Clients(baseUri + "/callback", oidcClient, bearerClient);
return new Config(clients);
}
And I update the security config (notice the shallow copy of the config and the setLoadProfilesFromSession(false) on a new DefaultSecurityLogic():

@Override
public void addInterceptors(final InterceptorRegistry registry) {
// the /protected/** URLs require the OIDC authentication
addSecurity(registry, "OidcClient").addPathPatterns("/protected/**");

final var newConfig = config().withSecurityLogic(new DefaultSecurityLogic().setLoadProfilesFromSession(false));
addSecurityWithConfig(registry, newConfig, "DirectBearerAuthClient").addPathPatterns("/rest/**");
}

I'm not sure this is feasible with dropwizard-pac4j though...

** the JwtAuthenticator should validate the exp claim. If not, please submit a PR with a test demonstrating the bug.

Thanks.
Best regards,
Jérôme


--
You received this message because you are subscribed to the Google Groups "Pac4j development mailing list" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pac4j-dev+...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/pac4j-dev/3d718315-637a-4bd5-bf4e-8470cbb96a58n%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages