Skip to content

Ensure consistent transaction rollback across all error response paths#50943

Merged
rmartinc merged 1 commit into
keycloak:mainfrom
mabartos:KC-50911
Jul 24, 2026
Merged

Ensure consistent transaction rollback across all error response paths#50943
rmartinc merged 1 commit into
keycloak:mainfrom
mabartos:KC-50911

Conversation

@mabartos

@mabartos mabartos commented Jul 16, 2026

Copy link
Copy Markdown
Member
  • Closes Ensure consistent transaction rollback across all error response paths #50911
  • Always rollback TX when the error is thrown by default - It helps to make Keycloak more consistent and not persist any side data that can change the state during the bad request phase
  • For some cases, we need to persist(commit TX) some data - brute force detector, error events, token revocations,...
  • most of them already run in a separate TX that does the commit, but for some of them, we needed to wrap them in a separate TX as well

@shawkins

Copy link
Copy Markdown
Contributor

Looks promising @mabartos

@mabartos
mabartos force-pushed the KC-50911 branch 5 times, most recently from acd82f7 to ea6dccf Compare July 17, 2026 11:23
@mabartos mabartos self-assigned this Jul 17, 2026
@mabartos mabartos added the status/hold PR should not be merged. On hold for later. label Jul 20, 2026
@mabartos
mabartos marked this pull request as ready for review July 20, 2026 10:40
@mabartos
mabartos requested a review from a team as a code owner July 20, 2026 10:40
Copilot AI review requested due to automatic review settings July 20, 2026 10:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Ensures Keycloak error helpers consistently roll back request transactions while preserving selected security-related side effects in independent transactions.

Changes:

  • Centralizes rollback behavior for error pages and exceptions.
  • Adds fresh-session lookup and transaction utilities.
  • Moves session invalidation and permission-ticket persistence into separate transactions.

Reviewed changes

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

Show a summary per file
File Description
services/.../AsyncResponseTransaction.java Preserves error responses after rollback.
services/.../RollbackWebApplicationException.java Adds rollback-aware exception base.
services/.../ErrorResponseException.java Uses the rollback-aware base.
services/.../ErrorPageException.java Uses centralized rollback handling.
services/.../ErrorPage.java Marks error-page transactions rollback-only.
services/.../CorsErrorResponseException.java Rolls back CORS error paths.
services/.../TokenManager.java Persists session invalidation separately.
services/.../AbstractRefreshTokenProvider.java Persists client-session detachment.
services/.../ResourceOwnerPasswordCredentialsGrantType.java Separately removes authentication sessions.
services/.../AuthorizationCodeGrantType.java Separately invalidates reused-code sessions.
server-spi-private/.../SessionLookup.java Adds fresh-session entity lookups.
server-spi-private/.../KeycloakModelUtils.java Adds independent transaction helper.
server-spi-private/.../PermissionTicketAwareDecisionResultCollector.java Persists denied-request tickets separately.

Comment thread services/src/main/java/org/keycloak/services/ErrorPage.java
@mabartos mabartos removed the status/hold PR should not be merged. On hold for later. label Jul 20, 2026
@mabartos

Copy link
Copy Markdown
Member Author

@rmartinc @shawkins @ahus1 Could anyone review it, please? Thanks!

@mabartos
mabartos requested review from ahus1, rmartinc and shawkins July 20, 2026 12:11
@shawkins

Copy link
Copy Markdown
Contributor

@mabartos have there been inconsistencies for a while?

Looking at the current undertow logic, which seems to be what was used in Wildfly as well, on any uncaught exception the transaction would be rolled back -

KeycloakModelUtils.runJobInTransaction(factory, session -> {
and respectively.

That would have rolled back anything that throw any WebApplicationException regardless of whether it contained a Response.

If I understand correctly the current state is that WebApplicationExceptions thrown containing a Response, will not roll back the transaction.

So the changes here attempt to make it more like it used to be - any RollbackWebApplicationException or exception handled by the KeycloakErrorHandler will cause the transaction to be rolled back.

Are there some WebApplicationExceptions thrown where we don't expect a rollback? Or do we need to make sure that all the WebApplicationException we throw use RollbackWebApplicationException as the base class?

most of them already run in a separate TX that does the commit, but for some of them, we needed to wrap them in a separate TX as well

Ideally we should avoid the paradigm of suspending and starting a new transaction. Is it possible that this could be handled as an enlistAfterCompletion transaction in the rollback method?

@mabartos

Copy link
Copy Markdown
Member Author

@shawkins Thanks for the review!

have there been inconsistencies for a while?

Yes, we detected the behavior in the #50850, so there might be more cases where it can provide such a security issue - IMO, that's why we need to make it more robust.

That would have rolled back anything that throw any WebApplicationException regardless of whether it contained a Response. If I understand correctly the current state is that WebApplicationExceptions thrown containing a Response, will not roll back the transaction.

Yes, exactly. Our KeycloakErrorHandler is not called when the WebApplicationException contains a response; otherwise the rollback is set for every error on KC side.

So the changes here attempt to make it more like it used to be - any RollbackWebApplicationException or exception handled by the KeycloakErrorHandler will cause the transaction to be rolled back.

Yes, we attempt to cover all error cases and always set TX rollback.

Are there some WebApplicationExceptions thrown where we don't expect a rollback? Or do we need to make sure that all the WebApplicationException we throw use RollbackWebApplicationException as the base class?

I did an analysis, and there should not be any, where we explicitly expect a commit of the main transaction. There are some places, where we need to store soma data along the way - brute force detector, error events, UMA, session invalidations. It'd be good if any new KC WAE exception would extend the RollbackWebApplicationException. And if there's some case the rollback is not wanted, it can be workarounded by the plain WAE. Maybe we could fill an issue on the RESTEasy side to always execute exception mappers even for the WAE with response.

Ideally we should avoid the paradigm of suspending and starting a new transaction. Is it possible that this could be handled as an enlistAfterCompletion transaction in the rollback method?

Yes, that might be a more suitable approach. I'll look into it. Thanks!

@mabartos
mabartos marked this pull request as draft July 22, 2026 07:35
Closes keycloak#50911

Signed-off-by: Martin Bartoš <mabartos@redhat.com>
@mabartos
mabartos marked this pull request as ready for review July 22, 2026 08:54
Copilot AI review requested due to automatic review settings July 22, 2026 08:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

@keycloak-github-bot keycloak-github-bot Bot 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.

Unreported flaky test detected, please review

@keycloak-github-bot

Copy link
Copy Markdown

Unreported flaky test detected

If the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR.

org.keycloak.testsuite.adapter.servlet.SAMLServletAdapterTest#salesMetadataTest

Keycloak CI - Adapter IT Strict Cookies

org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertTrue.failNotTrue(AssertTrue.java:63)
	at org.junit.jupiter.api.AssertTrue.assertTrue(AssertTrue.java:36)
...

Report flaky test

org.keycloak.testsuite.adapter.servlet.SAMLServletAdapterTest#employeeSigPostNoIdpKeyTestNoKeyNameInKeyInfo

Keycloak CI - Adapter IT Strict Cookies

org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertTrue.failNotTrue(AssertTrue.java:63)
	at org.junit.jupiter.api.AssertTrue.assertTrue(AssertTrue.java:36)
...

Report flaky test

org.keycloak.testsuite.adapter.servlet.SAMLServletAdapterTest#salesPostEncTest

Keycloak CI - Adapter IT Strict Cookies

org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertTrue.failNotTrue(AssertTrue.java:63)
	at org.junit.jupiter.api.AssertTrue.assertTrue(AssertTrue.java:36)
...

Report flaky test

@mabartos

Copy link
Copy Markdown
Member Author

@shawkins Should be ready for review. Thanks!

@shawkins shawkins left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe we could fill an issue on the RESTEasy side to always execute exception mappers even for the WAE with response.

That feels more like a sure thing. Otherwise the only other thought is whether our CloseSessionFilter could handle the detection of this situation - some way to see if the context has been aborted for example.

Otherwise, everything looks good. Thanks @mabartos

@mabartos

Copy link
Copy Markdown
Member Author

That feels more like a sure thing. Otherwise the only other thought is whether our CloseSessionFilter could handle the detection of this situation - some way to see if the context has been aborted for example.

@shawkins Thanks! I'll create an issue on their side. I thought about the CloseSessionFilter as well, but for now, I just chose to have an approach with more freedom (the Rollback WAE can help you but if you need to do some special stuff...) to not break existing deployments in the community. Maybe we can revisit later if we find some more problems.

@mabartos

Copy link
Copy Markdown
Member Author

@shawkins Some additional context: It seems we're quite tied up, as the behavior does not come from the RESTEasy, but rather from the JAX-RS spec:

"If the response property of the exception does not contain an entity and an exception mapping provider is available for WebApplicationException or the corresponding subclass, an implementation MUST use the provider to create a new Response instance, otherwise the response property is used directly." https://jakarta.ee/specifications/restful-ws/3.0/jakarta-restful-ws-spec-3.0.html#method_exc

So, we cannot break the compliance on the RESTEasy side, and probably not worth to go over the JAX-RS spec itself.

@rmartinc rmartinc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks @mabartos for the PR and @shawkins for the review! I think this behavior is more aligned with the idea of throwing exception means rollback. We can only trust on tests to see if there are some required changes that now are missing. So we will need to be aware of new issues triggered by this change. But we have time to detect any issue until 26.8. Approving.

@rmartinc
rmartinc merged commit ac85144 into keycloak:main Jul 24, 2026
91 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ensure consistent transaction rollback across all error response paths

4 participants