Description
Describe the bug
I'm testing out the multitenant solution in my application so I can support both code-flow and bearer token for my JSF UI and REST API code. I want the REST endpoints to be accessible from curl, postman, etc. so I can't use the hybrid
type.
My JSF ui uses the web-app
service type and my REST apis use the service
type.
Things seem to be working ok and I can log in fine in the UI and also use postman to hit my rest endpoints passing an access token in the Authorization header.
However, I have a API method that I wanted to print out the token information that is available to me so I can learn what is actually provided during which calls.
I think I found a bug, in JsonWebToken
and OidcSession
when trying to access expirationDate.
Error:
2023-09-15 16:55:21,426 ERROR [io.qua.ver.htt.run.QuarkusErrorHandler] (executor-thread-1) HTTP Request to /api/tokeninfo failed, error id: 225fcb1e-3b82-4731-aec2-b982c3a26469-3: java.lang.NullPointerException: Cannot invoke "java.lang.Long.longValue()" because the return value of "org.eclipse.microprofile.jwt.JsonWebToken.getClaim(org.eclipse.microprofile.jwt.Claims)" is null
at org.eclipse.microprofile.jwt.JsonWebToken.getExpirationTime(JsonWebToken.java:98)
at org.eclipse.microprofile.jwt.OidcJsonWebTokenProducer_ProducerMethod_currentIdToken_fd05e4c440bc560635edfdf219600734450212ad_ClientProxy.getExpirationTime(Unknown Source)
at org.primefaces.babylon.service.SecurityService.printTokenInfo(SecurityService.java:80)
at org.primefaces.babylon.service.SecurityService_Subclass.printTokenInfo$$superforward(Unknown Source)
at org.primefaces.babylon.service.SecurityService_Subclass$$function$$12.apply(Unknown Source)
at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:73)
at io.quarkus.arc.impl.AroundInvokeInvocationContext$NextAroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:97)
Below is the service I am hitting from both the UI and the REST end, and the UI calls via JSF work fine, but the Postman doesn't.
It looks like there is some sort of proxy object calls JsonNullWebToken or something that causing the initial null check to pass but then internally it blows up trying to access other information.
This happens for both the OidcSession
and the JsonWebToken idToken
.
Since I'm hitting the API with an AccessToken only, I would assume the IdToken would be null, but it is not. Which is why I put the null check in there to try to fix it.
When I call the same method via JSF #{securityService.printTokenInfo()}
I see all the information print out which I expect.
@Named
@ApplicationScoped
@PermitAll
public class SecurityService implements Serializable {
@Inject
Logger log;
@Inject
SecurityIdentity identity;
@Inject
@IdToken
JsonWebToken idToken;
@Inject
JsonWebToken accessToken;
@Inject
OidcSession session;
public boolean isLoggedIn() {
log.infof("User %s is logged in = %s and has roles %s", identity.getPrincipal().getName(), !identity.isAnonymous(),identity.getRoles() );
return identity != null && !identity.isAnonymous();
}
public boolean hasRole(String role) {
return identity.hasRole(role);
}
public void printTokenInfo() {
Instant now = Instant.now();
Instant accessTokenExpiration = null;
Instant idTokenExpiration = null;
Duration accessTokenDuration = null;
Duration idTokenDuration = null;
if (accessToken != null) {
accessTokenExpiration = Instant.ofEpochSecond(accessToken.getExpirationTime());
accessTokenDuration = Duration.between(now, accessTokenExpiration);
log.infof("Access Token Duration is %d days, %d hours, %d minutes, and %d seconds.\n",
accessTokenDuration.toDays(),
accessTokenDuration.toHoursPart(),
accessTokenDuration.toMinutesPart(),
accessTokenDuration.toSecondsPart());
}
if (idToken != null) {
idTokenExpiration = Instant.ofEpochSecond(idToken.getExpirationTime());
idTokenDuration = Duration.between(now, idTokenExpiration);
log.infof("Info Token Duration is %d days, %d hours, %d minutes, and %d seconds.\n",
idTokenDuration.toDays(),
idTokenDuration.toHoursPart(),
idTokenDuration.toMinutesPart(),
idTokenDuration.toSecondsPart());
}
if (session != null && session.getIdToken() != null) {
log.infof("Session information for tenant [%s] with username [%s] expires at: [%s] and is valid for [%s] - Groups: %s Claims: %s",
session.getTenantId(), session.getIdToken().getName(),
session.expiresAt(), session.validFor(),
session.getIdToken().getGroups(), session.getIdToken().getClaimNames());
}
}
@Path("/api/tokeninfo")
@GET
@RolesAllowed("web-license-admin")
@Produces(MediaType.TEXT_PLAIN)
public Response doNothing() {
printTokenInfo();
return Response.ok().build();
}
}```
### Expected behavior
_No response_
### Actual behavior
_No response_
### How to Reproduce?
_No response_
### Output of `uname -a` or `ver`
_No response_
### Output of `java -version`
_No response_
### GraalVM version (if different from Java)
_No response_
### Quarkus version or git rev
3.3.2
### Build tool (ie. output of `mvnw --version` or `gradlew --version`)
_No response_
### Additional information
_No response_