8000 Fix SAML endpoint 500 on unauthenticated LogoutRequest with no logout URL by msaravanan77 · Pull Request #52592 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content

Fix SAML endpoint 500 on unauthenticated LogoutRequest with no logout URL - #52592

Open
msaravanan77 wants to merge 4 commits into
keycloak:mainfrom
msaravanan77:fix/saml-logout-npe-no-slo-url-51371
Open

msaravanan77 wants to merge 4 commits into
keycloak:mainfrom
msaravanan77:fix/saml-logout-npe-no-slo-url-51371

Conversation

@msaravanan77
@msaravanan77 msaravanan77 commented Sep 9, 2026 8000 •
Copy link
Copy Markdown

Closes #51371

Problem

SamlService.BindingProtocol.logoutRequest() falls through to its default POST/Redirect LogoutResponse path whenever the incoming LogoutRequest is unauthenticated (no identity cookie) and carries no matching session index. That path calls SamlProtocol.getLogoutServiceUrl(), which returns null when the client has no single-logout-service URL configured for the binding in use and no management URL to fall back to.

The null destination then flows into the response builders (builder.destination(null) / binding.redirectBinding(...).response(null)), where KeycloakUriBuilder.fromUri() throws IllegalArgumentException: uri parameter is null. It is wrapped into a RuntimeException and surfaces to the client as an unhandled HTTP 500 instead of a clean error.

Both HTTP-Redirect and HTTP-POST bindings are affected.

Fix

Guard the null/blank destination the same way the method already guards validateDestination() failures a few lines above: log an INVALID_SAML_LOGOUT_REQUEST event and return 400 INVALID_REQUEST. This matches the behaviour the issue asks for and is consistent with how the same method already rejects other unusable logout requests.

SOAP binding is excluded from the check since its response path does not need a destination URL.

String logoutBindingUri = SamlProtocol.getLogoutServiceUrl(session, client, logoutBinding, true);
if (! SamlProtocol.SAML_SOAP_BINDING.equals(logoutBinding) && (logoutBindingUri == null || logoutBindingUri.trim().isEmpty())) {
    event.detail(Details.REASON, "logout_binding_uri_missing");
    event.error(Errors.INVALID_SAML_LOGOUT_REQUEST);
    return error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_REQUEST);
}

Testing

  • LogoutTest.testLogoutNoLogoutServiceUrlConfiguredForBindingReturnsBadRequest reproduces the exact scenario: the sales-post client is configured with only its POST single-logout-service URL, then an unauthenticated (clearCookies()) LogoutRequest is sent over the REDIRECT binding — so there is no destination for the LogoutResponse. The test asserts a 400 response rather than a 500.
  • LogoutTest.testLogoutNoLogoutServiceUrlConfiguredForPostBindingReturnsBadRequest covers the mirror case: the client reconfigured with only its REDIRECT SLO URL, receiving an unauthenticated POST-bound LogoutRequest.

AI agent disclosure

This PR was authored with the assistance of an AI agent (Claude Code) working from a prompt. The author has reviewed and understands the change, verified it against the codebase, and is responsible for addressing review feedback.

🤖 Generated with Claude Code

… URL

SamlService.BindingProtocol.logoutRequest() falls through to its default
POST/Redirect response path whenever the request is unauthenticated and
carries no matching session index. That path calls
SamlProtocol.getLogoutServiceUrl(), which returns null when the client has
no logout service URL configured for the binding in use and no management
URL to fall back to. The null destination then reaches
KeycloakUriBuilder.fromUri(), which throws IllegalArgumentException,
surfacing to the client as an unhandled HTTP 500 instead of a clean error.

Guard the null/blank destination the same way the method already guards
validateDestination() failures a few lines above: log an
INVALID_SAML_LOGOUT_REQUEST event and return 400 INVALID_REQUEST. SOAP
binding is excluded from the check since it doesn't need a destination
URL.

Adds a regression test to LogoutTest exercising the exact scenario: an
unauthenticated logout request (cookies cleared) sent via REDIRECT
binding to a client that only has its POST logout service URL configured.

Closes keycloak#51371

Signed-off-by: msaravanan77 <msaravanan77@gmail.com>
Copilot AI balanced review requested due to automatic review settings September 9, 2026 14:45
@msaravanan77
msaravanan77 requested review from a team as code owners September 9, 2026 14:45
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.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Pull request overview

Prevents unhandled HTTP 500s when processing unauthenticated SAML LogoutRequests for clients that lack a logout service URL for the request binding, returning a consistent 400 INVALID_REQUEST instead.

Changes:

  • Add a guard in SamlService.logoutRequest() to detect missing/blank logout destination URLs (non-SOAP) and return 400 INVALID_REQUEST.
  • Add an integration test reproducing the REDIRECT-binding case when only a POST logout service URL is configured.

Reviewed changes

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

File Description
services/src/main/java/org/keycloak/protocol/saml/SamlService.java Adds a defensive check for missing logout destination URL to avoid builder errors and return a 400.
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/LogoutTest.java Adds a regression test ensuring the scenario returns 400 instead of 500.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

…th no logout URL

Follow-up to the previous commit addressing PR review feedback: the fix
guards both bindings, but only the REDIRECT-bound scenario was covered by
a test. Add the mirror case - the "sales-post" client reconfigured with
only its REDIRECT single-logout-service URL, receiving an unauthenticated
POST-bound LogoutRequest - which must likewise be rejected with 400
INVALID_REQUEST rather than failing with a 500.

Signed-off-by: msaravanan77 <msaravanan77@gmail.com>

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 9, 2026 15:16
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.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Pull request overview

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

Copilot AI review requested due to automatic review settings September 14, 2026 16:12
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.

🟢 Approval recommended

The focused guard addresses the failure safely, and both affected bindings have regression coverage.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced (auto)

Note

Copilot is running an experiment and ran this review at Balanced.

Copilot AI review requested due to automatic review settings September 19, 2026 00:45
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.

Copilot review overview

🟢 Approval recommended

The targeted validation correctly prevents the reported exception and is covered for both affected bindings.

Review effort: Balanced
Findings: None

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.

SAML endpoint returns HTTP 500 on an unauthenticated LogoutRequest when the client has no logout service URL

2 participants

0