Skip to content

Support for OIDC RP-Initiated form post logout mode #48575

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 30, 2025

Conversation

sberyozkin
Copy link
Member

@sberyozkin sberyozkin commented Jun 24, 2025

Fixes #48435.

This PR supports an OIDC RP-Initiated logout mode where the logout parameters are auto-submitted to the OIDC logout endpoint as form parameters.

FYI, initially, I thought that in this case, I'd write the response direct in the CodeAuthenticationMechanism, but then I thought, even though it is not an actual 302 redirect, it is still an implicit redirect via the auto-submitting HTML form and if users have AuthenticationRedirectException mappers then they should still be able to catch such exceptions, and also should be able to filter such implicit redirects.

Initial idea how to to pass the form post logout content alongside AuthenticationRedirectException:

I ended up passing the form post logout data as a request context property that AuthenticationRedirectException handlers can access from RoutingContext

How it was implemented in the end:

I simply pass the form post logout content that actually inlines OIDC logout endpoint URL as the AuthenticationRedirectException redirect URI parameter but with the 200 code, and the handlers can distinguish how to respond given the code. Injecting RoutingContext into the exception mappers may not work and dealing with RoutingContext is more complex.

Michal, @michalvavrik, I believe we can think how to extend AuthenticationRedirectException to represent the form post logout content cleaner in one of the next quarkus-security api releases.

I've worked on the test today with HtmlUnit and it works well with the automatic form submission.

CC @calvernaz

Copy link

github-actions bot commented Jun 24, 2025

🙈 The PR is closed and the preview is expired.

@sberyozkin sberyozkin force-pushed the oidc_logout_post_method branch 3 times, most recently from 3e791db to 7d70ebd Compare June 26, 2025 16:28
@sberyozkin sberyozkin force-pushed the oidc_logout_post_method branch from 7d70ebd to d2c9ec1 Compare June 26, 2025 16:44
@sberyozkin sberyozkin marked this pull request as ready for review June 26, 2025 16:55
@sberyozkin sberyozkin force-pushed the oidc_logout_post_method branch 2 times, most recently from f843bf2 to a4b0d5c Compare June 26, 2025 17:03

This comment has been minimized.

This comment has been minimized.

@sberyozkin sberyozkin force-pushed the oidc_logout_post_method branch from a4b0d5c to b76af3b Compare June 26, 2025 17:59

This comment has been minimized.

This comment has been minimized.

@sberyozkin
Copy link
Member Author

sberyozkin commented Jun 27, 2025

I just did a minor update to try to get the flaky OidcClientTest test which checks the configured expiry time passing, which is absolutely not related to this PR

This comment has been minimized.

This comment has been minimized.

@sberyozkin sberyozkin force-pushed the oidc_logout_post_method branch from a035762 to 8c6c2a4 Compare June 28, 2025 18:54
@sberyozkin
Copy link
Member Author

I enhanced the existing test and also did play for a while with trying to do a similar test for Quarkus REST with Keycloak directly - but it appears the form post logout does not work with Keycloak right now... But indeed, both REST and Resteasy exception mappers are identical now, so the test should be enough I hope...

Thanks

This comment has been minimized.

This comment has been minimized.

@sberyozkin
Copy link
Member Author

@michalvavrik @gastaldi Hi, the failing test is definitely not related, I can run the build again, but I'm not sure it is necessary

@sberyozkin sberyozkin requested a review from gastaldi June 30, 2025 08:58
@gastaldi gastaldi requested a review from Copilot June 30, 2025 13:02
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Adds support for the OIDC RP-Initiated form-post logout mode by extending configuration, builder APIs, runtime handling, and tests.

  • Introduce a LogoutMode enum with new config and builder methods.
  • Implement form-post logout HTML generation via LogoutUtils and throw AuthenticationRedirectException(200, ...).
  • Update HTTP handlers and exception mappers to emit the auto-submitting form, and add an integration test verifying the flow.

Reviewed Changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
integration-tests/oidc-tenancy/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java Added testFormPostLogoutWebApp() to verify form-post logout behavior
integration-tests/oidc-tenancy/src/main/resources/application.properties Configured logout endpoints, paths, and set logout-mode=form-post
integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/TenantResource.java Added formPostPostLogout endpoint to confirm logout
integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/OidcResource.java Simulates OIDC provider’s form-post logout handler
integration-tests/oidc-client-wiremock/src/test/java/io/quarkus/it/keycloak/OidcClientTest.java Adjusted expiry assertion bounds for token tests
extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpSecurityRecorder.java Emit form-post content on HTTP 200 in AuthenticationRedirectException
extensions/resteasy-reactive/rest/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/exceptionmappers/AuthenticationRedirectExceptionMapper.java Mapper now embeds form-post body for code 200
extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/AuthenticationRedirectExceptionMapper.java Classic REST mapper updated similarly
extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcTenantConfigImpl.java Extended test config mapping to record logoutMode
extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/builders/LogoutConfigBuilder.java Added logoutMode(...) builder methods
extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcTenantConfig.java Defined LogoutMode enum and new logoutMode() config property
extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java Branch for form-post logout flow in code flow mechanism
extensions/oidc/runtime/src/main/java/io/quarkus/oidc/LogoutUtils.java Utility to build auto-submitting HTML form for logout
docs/src/main/asciidoc/security-oidc-expanded-configuration.adoc Documentation for quarkus.oidc.logout.logout-mode
Comments suppressed due to low confidence (3)

extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/builders/LogoutConfigBuilder.java:145

  • This Javadoc comment refers to clearSiteData but is placed on logoutMode(...). Update the @param description to match the logoutMode parameter or remove it for the no-arg overload.
     * @param clear site data directives {@link OidcTenantConfig.Logout#clearSiteData()}

integration-tests/oidc-tenancy/src/main/resources/application.properties:58

  • The end-session-path property is placed outside of the logout namespace. It should likely be quarkus.oidc.tenant-web-app.logout.end-session-path to be picked up by the logout config.
quarkus.oidc.tenant-web-app.end-session-path=http://localhost:8081/oidc/form-post-logout

integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/OidcResource.java:384

  • For the form-post logout simulation, this returns a 303 redirect instead of an auto-submitting HTML form. You should return a 200 response with the generated form HTML to match the intended flow.
        return Response.seeOther(URI.create(postLogoutRedirectUri + "?username=" + userName)).build();

@sberyozkin
Copy link
Member Author

Hey @gastaldi, I'll take all those 3 suggestions :-), thanks. Let me apply, I believe @michalvavrik is good with the rest of the changes, given his earlier feedback

@sberyozkin sberyozkin force-pushed the oidc_logout_post_method branch from 33d1042 to bcb8215 Compare June 30, 2025 15:42
@sberyozkin sberyozkin force-pushed the oidc_logout_post_method branch from bcb8215 to c69ce54 Compare June 30, 2025 15:46
@sberyozkin
Copy link
Member Author

@gastaldi Interesting, Copilot is not aware of our formatter rules :-)

Copy link

quarkus-bot bot commented Jun 30, 2025

Status for workflow Quarkus Documentation CI

This is the status report for running Quarkus Documentation CI on commit c69ce54.

✅ The latest workflow run for the pull request has completed successfully.

It should be safe to merge provided you have a look at the other checks in the summary.

Warning

There are other workflow runs running, you probably need to wait for their status before merging.

Copy link

quarkus-bot bot commented Jun 30, 2025

Status for workflow Quarkus CI

This is the status report for running Quarkus CI on commit c69ce54.

✅ The latest workflow run for the pull request has completed successfully.

It should be safe to merge provided you have a look at the other checks in the summary.

You can consult the Develocity build scans.


Flaky tests - Develocity

⚙️ Native Tests - Observability

📦 integration-tests/opentelemetry

io.quarkus.it.opentelemetry.MetricsIT.testAllJvmMetrics - History

  • Assertion condition defined as a Lambda expression in io.quarkus.it.opentelemetry.MetricsTest The metric jvm.cpu.longlock within 10 seconds. - org.awaitility.core.ConditionTimeoutException
org.awaitility.core.ConditionTimeoutException: Assertion condition defined as a Lambda expression in io.quarkus.it.opentelemetry.MetricsTest The metric jvm.cpu.longlock within 10 seconds.
	at org.awaitility.core.ConditionAwaiter.await(ConditionAwaiter.java:167)
	at org.awaitility.core.AssertionCondition.await(AssertionCondition.java:119)
	at org.awaitility.core.AssertionCondition.await(AssertionCondition.java:31)
	at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:1160)
	at org.awaitility.core.ConditionFactory.untilAsserted(ConditionFactory.java:790)
	at io.quarkus.it.opentelemetry.MetricsTest.lambda$testAllJvmMetrics$6(MetricsTest.java:111)
	at java.base/java.lang.Iterable.forEach(Iterable.java:75)
  • Assertion condition defined as a Lambda expression in io.quarkus.it.opentelemetry.MetricsTest The metric jvm.cpu.longlock within 10 seconds. - org.awaitility.core.ConditionTimeoutException
org.awaitility.core.ConditionTimeoutException: Assertion condition defined as a Lambda expression in io.quarkus.it.opentelemetry.MetricsTest The metric jvm.cpu.longlock within 10 seconds.
	at org.awaitility.core.ConditionAwaiter.await(ConditionAwaiter.java:167)
	at org.awaitility.core.AssertionCondition.await(AssertionCondition.java:119)
	at org.awaitility.core.AssertionCondition.await(AssertionCondition.java:31)
	at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:1160)
	at org.awaitility.core.ConditionFactory.untilAsserted(ConditionFactory.java:790)
	at io.quarkus.it.opentelemetry.MetricsTest.lambda$testAllJvmMetrics$6(MetricsTest.java:111)
	at java.base/java.lang.Iterable.forEach(Iterable.java:75)

⚙️ JVM Integration Tests - JDK 17 Windows

📦 integration-tests/grpc-hibernate

com.example.grpc.hibernate.VertxBlockingRawTest.shouldAdd - History

  • Condition with Lambda expression in com.example.grpc.hibernate.BlockingRawTestBase was not fulfilled within 30 seconds. - org.awaitility.core.ConditionTimeoutException
org.awaitility.core.ConditionTimeoutException: Condition with Lambda expression in com.example.grpc.hibernate.BlockingRawTestBase was not fulfilled within 30 seconds.
	at org.awaitility.core.ConditionAwaiter.await(ConditionAwaiter.java:167)
	at org.awaitility.core.CallableCondition.await(CallableCondition.java:78)
	at org.awaitility.core.CallableCondition.await(CallableCondition.java:26)
	at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:1160)
	at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:1129)
	at com.example.grpc.hibernate.BlockingRawTestBase.shouldAdd(BlockingRawTestBase.java:59)
	at java.base/java.lang.reflect.Method.invoke(Method.java:569)

@sberyozkin sberyozkin merged commit 483f8fe into quarkusio:main Jun 30, 2025
60 checks passed
@quarkus-bot quarkus-bot bot added this to the 3.25 - main milestone Jun 30, 2025
@sberyozkin sberyozkin deleted the oidc_logout_post_method branch June 30, 2025 21:00
@quarkus-bot quarkus-bot bot added the kind/enhancement New feature or request label Jun 30, 2025
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.

Support for the form post redirect mode with RP Initiated logout
4 participants