Skip to content

fix ChainAuthHandler.any() is interrupted by JWTAuthHandler's postAuthentication#2875

Open
ayhanap wants to merge 2 commits into
vert-x3:masterfrom
ayhanap:issues/2691
Open

fix ChainAuthHandler.any() is interrupted by JWTAuthHandler's postAuthentication#2875
ayhanap wants to merge 2 commits into
vert-x3:masterfrom
ayhanap:issues/2691

Conversation

@ayhanap

@ayhanap ayhanap commented May 2, 2026

Copy link
Copy Markdown

Motivation:

Solves #2691.

Problem Brief:

In ChainAuthHandler.any(), the chain commits to the first handler whose authenticate() succeeds, even if that handler's postAuthentication() later rejects the request. This breaks setups like two JWTAuthHandlers with different required scopes:

ChainAuthHandler.any()
  .add(JWTAuthHandler.create(jwt).withScope("users:read"))
  .add(JWTAuthHandler.create(jwt).withScope("users:all"));

A token carrying only users:all authenticates against the first handler, then gets rejected with 403 for the scope mismatch — the second handler is never tried, even though it would have accepted the request.

PR Details:

Instead of calling postAuthentication methods for authHandlers in the chain after the authentication succeeds, it now iterates authHandlers in authenticate and also calls postAuthentication for each of them in case of one might fail the context on postAuthentication. Implemented a context wrapper so we can catch the ctx.fail() calls and continue with the next authHandler in the chain instead of failing fast without trying the next in line.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses #2691 by changing ChainAuthHandler.any() so a handler that authenticates but later rejects during postAuthentication() (e.g., JWT scope checks) does not prevent subsequent handlers in the chain from being tried.

Changes:

  • Update ChainAuthHandlerImpl to run each candidate handler’s postAuthentication() during authenticate() in any() mode, allowing iteration to continue when postAuthentication() rejects with a recoverable status.
  • Add a regression test covering multiple JWTAuthHandler instances with different required scopes.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
vertx-web/src/main/java/io/vertx/ext/web/handler/impl/ChainAuthHandlerImpl.java Alters any() chain behavior by integrating postAuthentication() into the authenticate-iteration flow via a routing-context decorator.
vertx-web/src/test/java/io/vertx/ext/web/tests/handler/ChainAuthHandlerTest.java Adds a reproducer test for multiple JWT handlers with differing scope requirements.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +123 to +131
final PostAuthenticationCapture wrapper = new PostAuthenticationCapture(ctx);
try {
authHandler.postAuthentication(wrapper);
} catch (RuntimeException e) {
wrapper.completion.tryFail(e);
}
wrapper.completion.future().onComplete(o -> {
if (o.succeeded() && Boolean.TRUE.equals(o.result())) {
// postAuthentication called next() -> this handler fully accepts the request
Comment on lines +118 to +122
// postAuthentication implementations rely on ctx.user() to return the authenticated
// user. The framework normally sets it after authenticate() succeeds; do it here so
// the inner handler observes the same state.
((UserContextInternal) ctx.userContext()).setUser(user);

Comment on lines +135 to +143
// postAuthentication called fail(...) (or threw): treat it like an authenticate
// failure and try the next handler in the chain when the status is recoverable.
final int sc = wrapper.statusCode;
final Throwable f = o.failed() ? o.cause() : wrapper.failure;
final Throwable err = f != null ? f : new HttpException(sc != 0 ? sc : 403);
if (isRecoverable(err) || isRecoverableStatus(sc)) {
iterate(idx + 1, ctx, null, err, handler);
} else {
handler.complete(null, err);
Comment on lines +187 to +188
// chained here either. So we just continue with the router pipeline.
ctx.next();
Comment thread vertx-web/src/test/java/io/vertx/ext/web/tests/handler/ChainAuthHandlerTest.java Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@tsegismont

Copy link
Copy Markdown
Member

@ayhanap I read the automated review and I'm concerned about merging this right now. I need some more time to look at the chain auth handler again, and I can't do this right now. I'll come back to it after 5.1 is out (which should happen soonish)

@ayhanap

ayhanap commented May 13, 2026

Copy link
Copy Markdown
Author

@tsegismont To make things clearer for you, I will try to respond and handle the comments that raised from automated review. I already checked them out but probably I will update the PR for some of them and comment on some.

@tsegismont

tsegismont commented May 13, 2026 via email

Copy link
Copy Markdown
Member

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants