fix ChainAuthHandler.any() is interrupted by JWTAuthHandler's postAuthentication#2875
fix ChainAuthHandler.any() is interrupted by JWTAuthHandler's postAuthentication#2875ayhanap wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
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
ChainAuthHandlerImplto run each candidate handler’spostAuthentication()duringauthenticate()inany()mode, allowing iteration to continue whenpostAuthentication()rejects with a recoverable status. - Add a regression test covering multiple
JWTAuthHandlerinstances 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.
| 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 |
| // 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); | ||
|
|
| // 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); |
| // chained here either. So we just continue with the router pipeline. | ||
| ctx.next(); |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
@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) |
|
@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. |
|
Thanks
Le mer. 13 mai 2026 à 17:00, Ayhan APAYDIN ***@***.***> a
écrit :
… *ayhanap* left a comment (vert-x3/vertx-web#2875)
<#2875 (comment)>
@tsegismont <https://github.com/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.
—
Reply to this email directly, view it on GitHub
<#2875 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AALOLNQOTGOA2CBTQCTV66D42SEYFAVCNFSM6AAAAACYOMBO5KVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHM2DINBSGI4DGNZYGE>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
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:
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.