Describe the bug
I'm building API w/ quarkus-vertx-web & quarkus-smallrye-jwt. When I try call get claims values fromJsonWebToken inside Uni/ Multi callback they're always null like this:
@Route(path = "/tickets/:id", methods = HttpMethod.GET)
void getTicket(RoutingContext ctx) {
UUID id = RouteUtil.getUUIDParams(ctx, "id");
log.info(">>>>>>>> " + jwt.getName()); // <== this return correct value
ticketSvc.getOneById(id)
.subscribe()
.with(ThrowingConsumer.unchecked(ticket -> {
if (ticket == null) {
ctx.fail(HttpStatus.SC_NOT_FOUND);
} else if (!Objects.equals(ticket.getUserId(), jwt.getName())) {
ctx.fail(HttpStatus.SC_FORBIDDEN);
} else {
log.info("<<<<<<<<< " + jwt.getName()); // <== this return null
ctx.response()
.setStatusCode(HttpStatus.SC_OK)
.putHeader(HttpHeaders.CONTENT_TYPE, "application/json")
.end(mapper.writeValueAsString(ticket));
}
}),
ctx::fail);
}
Expected behavior
Should return correct claims value
Configuration
# Add your application.properties here, if applicable.
mp.jwt.verify.publickey.location=https://${AUTH0_DOMAIN}/.well-known/jwks.json
mp.jwt.verify.issuer=https://${AUTH0_DOMAIN}/
smallrye.jwt.path.groups=permissions
smallrye.jwt.groups-separator=','
smallrye.jwt.always-check-authorization=true
This looks like a context propagation issue. Perhaps jwt is a request-scoped bean?
Yes, it is request scoped
In that case you need context propagation, but with Uni it should be automatic, no, @cescoffier ?
@KienDangTran You've mentioned in Zulip that it works in one part of your code while does not work above. Can you please post the complete code showing where it works and where it does not
I already created a similar issue #8538
It has a reproducer
@sberyozkin As I mentioned, It works when it's called outside Uni/Multi callback handler and doesnt work when it's inside. Here are sample code:
@Slf4j
@RequestScoped
@RouteBase(path = "/base")
public class TicketRoutes {
private final ObjectMapper mapper;
private final TicketService ticketSvc;
private final JsonWebToken jwt;
public TicketRoutes(TicketService ticketSvc, JsonWebToken jwt) {
this.ticketSvc = ticketSvc;
this.jwt = jwt;
this.mapper = DatabindCodec.mapper()
.registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.setDefaultPropertyInclusion(
JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.ALWAYS))
}
@Route(path = "/tickets", methods = HttpMethod.POST, consumes = "application/json")
void postTicket(RoutingContext ctx) throws IOException {
Ticket ticket = mapper.readValue(ctx.getBody().getBytes(), Ticket.class);
log.info( // <= this returns correct claim values
"Issuer: {}\nName: {}\nGroups: {}\nAudience: {}",
jwt.getIssuer(),
jwt.getName(),
jwt.getGroups(),
jwt.getAudience());
ticketSvc
.createTicket(ticket)
.subscribe()
.with(
createdTicket ->
ctx.response()
.setStatusCode(HttpStatus.SC_CREATED)
.putHeader(HttpHeaders.CONTENT_TYPE, "application/json")
.end(mapper.writeValueAsString(createdTicket)),
ctx::fail);
}
}
@Slf4j
@RequestScoped
public class TicketService {
private final TicketRepository ticketRepo;
private final JsonWebToken jwt;
public TicketService(TicketRepository ticketRepo, JsonWebToken jwt) {
this.issueRepo = issueRepo;
this.ticketRepo = ticketRepo;
this.jwt = jwt;
}
public Uni<Ticket> createTicket(Ticket ticket) {
log.info( // <= this returns NULL values
"Issuer: {}\nName: {}\nGroups: {}\nAudience: {}",
jwt.getIssuer(),
jwt.getName(),
jwt.getGroups(),
jwt.getAudience());
return ticketRepo.create(ticket.withUserId(jwt.getName())).map(ticket::withId);
}
}
@KienDangTran You've mentioned in Zulip that it works in one part of your code while does not work above. Can you please post the complete code showing where it works and where it does not
OK, I've investigated and this works if you import the extension quarkus-smallrye-context-propagation AND you use RESTEasy.
When using reactive routes, it appears context propagation doesn't work, and I guess it's perhaps because something is terminating the request context prematurely. Could that be the case @mkouba ?
To be clear, using reactive routes, context propagation works for other contexts than the ArC request scope.
I guess it's perhaps because something is terminating the request context prematurely. Could that be the case?
AFAIK the request context is terminated when the route method completes. So yes, it's very likely the case.
@FroMage @cescoffier so I wonder if we should terminate the request context unless a route method returns Uni or Multi? What else is needed to support the context propagation properly?
Yes, it should be terminated when the Uni/Multi "terminates" (on().termination())
Yup, this is due to RouteHandler:
try {
requestContext.activate();
if (user != null) {
Arc.container().beanManager().fireEvent(user.getSecurityIdentity());
}
invoke(context);
} finally {
requestContext.terminate();
}
My advice is that you should register a handler for when the request is over:
@Override
default void handle(RoutingContext context) {
QuarkusHttpUser user = (QuarkusHttpUser) context.user();
ManagedContext requestContext = Arc.container().requestContext();
//todo: how should we handle non-proactive authentication here?
if (requestContext.isActive()) {
if (user != null) {
Arc.container().beanManager().fireEvent(user.getSecurityIdentity());
}
invoke(context);
} else {
requestContext.activate();
context.response().endHandler(v -> requestContext.terminate());
if (user != null) {
Arc.container().beanManager().fireEvent(user.getSecurityIdentity());
}
invoke(context);
}
}
Actually it's a little bit more complicated than this, because calling endHandler might override the previous handler silently. Hence why https://github.com/vert-x3/vertx-web/pull/1593 will support multiple handlers, and why @stuartwdouglas wrote https://github.com/quarkusio/quarkus/blob/4239c357540c0d19d96095ff0ebc98affe950ace/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/filters/ShutdownRequestWrapper.java#L58
We could wrap the request (or response) to do like Stuart, but I'm not sure where in this case.
@stuartwdouglas you have an idea?
Apparently this is coming soon in vert.x 3.9.1, so maybe just wait?
Note that it still needs to be deactivated after the request, just not terminated.
Yup. OK let's wait.
Vert.x 3.9.1 was released, so as soon as we upgrade in Quarkus, we can use it to fix this bug.
Vert.x 3.9.1 is now in master so we can fix this bug.
@FroMage I'm on it...
Great, thanks a lot.
Most helpful comment
Vert.x 3.9.1 was released, so as soon as we upgrade in Quarkus, we can use it to fix this bug.