Skip to content

SAML: return to original front-end URL after login (default RelayState) + fix logout redirect fall-through #36591

Description

@dsolistorres

Follow-up to #32365 / PR #36590 (which broke the infinite loop and added the default 403 "Access Denied" page + deterministic loop-guard). Two related improvements were deliberately deferred:

1. Return the user to the page they requested (default RelayState)

Today, when a front-end SAML user authenticates, the original front-end URL is lost across the IdP round-trip and the callback defaults to DotSamlConstants.DEFAULT_LOGIN_PATH = /dotAdmin/:

  • The RelayState dotCMS sends is built from the auth.relaystate velocity template; if not configured (the default), an empty RelayState is sent, so DotSamlResource.processLogin gets RelayState == null.
  • The session fallback (REDIRECT_AFTER_LOGIN) is also null on the callback because the session doesn't survive the cross-site IdP POST (SameSite cookies → fresh session).
  • With no redirect.after.login config set, it defaults to /dotAdmin/ → a front-end-only user loops (now caught by the loop-guard as a 403, but they still don't reach their page).

Proposed fix: default the RelayState to the original request URI (ORIGINAL_REQUEST) when no auth.relaystate template is configured, so the return URL survives the round-trip via the SAML RelayState param (which the IdP echoes reliably).

Security requirement: DotSamlResource.processLogin currently redirects straight to the RelayState value (RedirectUtil.sendRedirectHTML(resp, loginPath)). Before honoring an auto-derived RelayState we must add open-redirect validation — only allow relative / same-host paths — otherwise it becomes a redirect vulnerability.

Needs a small design pass + testing across IdPs (Entra ID, Okta).

Immediate workaround (no code): set redirect.after.login (or an auth.relaystate template) on the SAML app to a front-end URL so front-end users land there instead of /dotAdmin/.

2. Fix logout redirect fall-through (spurious DoesNotExistException)

DotSamlResource.logoutGet and logoutPost do not return after RedirectUtil.sendRedirectHTML(...), so after sending the redirect they fall through and always throw new DoesNotExistException("No idpConfig …") — even when the idpConfig was found and the redirect succeeded. The redirect still works (response already committed) but it logs a misleading error every logout. Fix: add return; after the redirect in both methods.

Refs: #32365, #36541, #36590

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status
    New

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions