Skip to content

fix: cookie-less cross-domain SSO login flow#1084

Merged
superdav42 merged 1 commit into
mainfrom
fix/sso-cookie-less-login-2.0.11
May 4, 2026
Merged

fix: cookie-less cross-domain SSO login flow#1084
superdav42 merged 1 commit into
mainfrom
fix/sso-cookie-less-login-2.0.11

Conversation

@superdav42

@superdav42 superdav42 commented May 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Enables SSO login from subsite to main site without requiring 3rd party cookies.

Problem

When visiting a subsite (e.g., vidanuevanaz.org) wp-admin, users were redirected to the main site (mygratis.site) login page. After logging in, the redirect back to the subsite didn't work without 3rd party cookies enabled.

Solution

  1. Token-based authentication: After login on main site, generate a time-limited wu_sso_token and add it to the return URL when redirecting to a different domain.

  2. Subsite token validation: The multi-tenancy plugin's handle_sso_login() now accepts simpler token format from the main site's SSO handler.

  3. Already-logged-in handling: When user is already logged in on main site and visits login page with SSO params, redirect directly to subsite with token instead of showing "already logged in" message.

Changes

  • class-sso.php: Add generate_sso_token(), handle_login_redirect() extracts return_url from query params, handle_already_logged_in_on_login_page() handles logged-in users on login page
  • class-remote-sso-handler.php (multi-tenancy): Add validate_sso_token_from_main_site() to accept simpler token format

Testing

  1. Visit vidanuevanaz.org/wp-admin/ (unauthenticated)
  2. Should redirect to mygratis.site/login/?sso=login&return_url=https://vidanuevanaz.org
  3. Log in with credentials
  4. Should redirect back to vidanuevanaz.org - logged in

Also test already-logged-in flow:

  1. Already logged in on main site
  2. Visit subsite wp-admin
  3. Should redirect directly to subsite dashboard

aidevops.sh v3.14.51 plugin for OpenCode v1.14.33 with claude-sonnet-4-6 spent 15h 33m on this as a headless worker.

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced SSO authentication flow to properly recognize and handle return URL parameters during login.
    • Improved redirect destination extraction from URL query strings to ensure users are reliably directed to their intended location after successful authentication.
    • Refined redirect logic conditions to support more flexible parameter configurations for authentication flows.

When user is already logged in on main site and visits login page
with SSO params, redirect them directly to the subsite with a
verification token instead of showing 'already logged in' message.

- Check for 'sso' param in addition to 'return_url'
- Extract return_url from redirect_to query params if present
- Handle WP_Error user object in handle_login_redirect
@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The SSO login redirect handler handle_already_logged_in_on_login_page() now accepts both sso and return_url request parameters as triggers for SSO redirect flow. When return_url is missing, it is extracted from the redirect_to query string as a fallback. The early-exit condition was updated to proceed unless both parameters are absent.

Changes

SSO Login Redirect Parameter Handling

Layer / File(s) Summary
Parameter Extraction & Flow Control
inc/sso/class-sso.php
handle_already_logged_in_on_login_page() now reads the sso request parameter and ensures return_url is populated, falling back to extraction from redirect_to query string if missing. Early-exit condition updated to require the absence of both parameters before returning early; otherwise continues with existing domain comparison and tokenized redirect logic.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~18 minutes

Poem

A rabbit hops through login gates so fine,
Now SSO parameters align!
Return URL, when missing, shall appear,
From redirect's strings, crystal clear. 🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: enabling cookie-less cross-domain SSO login, which is the primary purpose and fix in this pull request.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sso-cookie-less-login-2.0.11

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
inc/sso/class-sso.php (1)

681-685: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

wu_sso_token has no single-use enforcement – replay attack is possible

generate_sso_token() produces an HMAC-signed JWT-style token that is valid for 5 minutes with no consumption record. A token URL captured via browser history, proxy/CDN logs, or Referer header can be replayed within that window to authenticate as the victim.

Store a short-lived transient (keyed by the HMAC or a random nonce) when the token is issued, and delete it on first successful validation in validate_sso_token_from_main_site():

// On issue (generate_sso_token):
$nonce = wp_generate_password(32, false);
set_transient('wu_sso_token_' . $nonce, $user_id, 300);
// Include $nonce in the token payload so the receiver can look it up and delete it.

// On validation (validate_sso_token_from_main_site):
if ( ! get_transient('wu_sso_token_' . $nonce) ) {
    return false; // already used or expired
}
delete_transient('wu_sso_token_' . $nonce);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@inc/sso/class-sso.php` around lines 681 - 685, The wu_sso_token currently can
be replayed because generate_sso_token() issues a short-lived token without a
consumption record; modify generate_sso_token() to include a
cryptographically-strong nonce (e.g., wp_generate_password) in the token payload
and store a short-lived transient keyed by that nonce (e.g.,
set_transient('wu_sso_token_' . $nonce, $user_id, 300)) when issuing the token,
and then update validate_sso_token_from_main_site() to extract the nonce from
the token, verify the transient exists via get_transient('wu_sso_token_' .
$nonce), reject if missing (used/expired), and delete the transient
(delete_transient) on first successful validation to enforce single-use.
🧹 Nitpick comments (1)
inc/sso/class-sso.php (1)

653-664: ⚡ Quick win

Duplicate return_url-from-redirect_to extraction – extract a shared helper

This block is a near-exact duplicate of lines 569–577 inside handle_login_redirect(). Extract to a private method (e.g., extract_return_url_from_redirect($redirect_to)) and call it from both places.

♻️ Proposed refactor
+	/**
+	 * Extracts return_url from a redirect_to query string parameter.
+	 *
+	 * `@since` 2.0.11
+	 *
+	 * `@param` string $redirect_to URL that may carry a return_url query param.
+	 * `@return` string Extracted and sanitized return_url, or empty string.
+	 */
+	private function extract_return_url_from_redirect(string $redirect_to): string {
+		if ( empty($redirect_to) ) {
+			return '';
+		}
+		$parsed = wp_parse_url($redirect_to, PHP_URL_QUERY);
+		if ( $parsed ) {
+			parse_str($parsed, $query_params);
+			if ( ! empty($query_params['return_url']) ) {
+				return esc_url_raw($query_params['return_url']);
+			}
+		}
+		return '';
+	}

Then in handle_already_logged_in_on_login_page():

-		// Also extract return_url from redirect_to if present
-		if ( empty($return_url) ) {
-			$redirect_to = $this->input('redirect_to', '');
-			if ( $redirect_to ) {
-				$parsed = wp_parse_url($redirect_to, PHP_URL_QUERY);
-				if ( $parsed ) {
-					parse_str($parsed, $query_params);
-					if ( ! empty($query_params['return_url']) ) {
-						$return_url = $query_params['return_url'];
-					}
-				}
-			}
-		}
+		// Also extract return_url from redirect_to if present
+		if ( empty($return_url) ) {
+			$return_url = $this->extract_return_url_from_redirect($this->input('redirect_to', ''));
+		}

And in handle_login_redirect() (lines 569-577):

-		if ( empty($return_url) && ! empty($redirect_to) ) {
-			$parsed = wp_parse_url($redirect_to, PHP_URL_QUERY);
-			if ( $parsed ) {
-				parse_str($parsed, $query_params);
-				if ( ! empty($query_params['return_url']) ) {
-					$return_url = $query_params['return_url'];
-				}
-			}
-		}
+		if ( empty($return_url) && ! empty($redirect_to) ) {
+			$return_url = $this->extract_return_url_from_redirect($redirect_to);
+		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@inc/sso/class-sso.php` around lines 653 - 664, The duplicate logic that pulls
a return_url from a redirect_to query string should be extracted into a private
helper (e.g., extract_return_url_from_redirect($redirect_to)) and used from both
handle_already_logged_in_on_login_page() and handle_login_redirect(); create
extract_return_url_from_redirect to accept the $redirect_to string, run
wp_parse_url + parse_str and return the return_url if present (or null/empty),
then replace the duplicated block in the code snippet (and the similar block at
lines 569–577 inside handle_login_redirect()) with a call to this new method to
centralize behavior and avoid duplication.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@inc/sso/class-sso.php`:
- Line 649: The code uses a hardcoded 'sso' literal when calling $this->input
which bypasses the wu_sso_get_url_path filter; change the call that sets
$sso_action to use $this->get_url_path() instead of the literal so the filtered
URL path is respected (i.e. replace $this->input('sso', '') with
$this->input($this->get_url_path(), '')); ensure this change is applied wherever
$sso_action is set so apply_filters('wu_sso_get_url_path', 'sso', ...) can take
effect.
- Line 660: The extracted $query_params['return_url'] is assigned to $return_url
without sanitization; update the code in the SSO handling (where parse_str
populates $query_params) to sanitize the value before assignment by running it
through a URL sanitizer (use esc_url_raw() per guidelines or wu_clean() as an
alternative) and ensure you check isset($query_params['return_url']) and provide
a safe fallback if missing/empty.

---

Outside diff comments:
In `@inc/sso/class-sso.php`:
- Around line 681-685: The wu_sso_token currently can be replayed because
generate_sso_token() issues a short-lived token without a consumption record;
modify generate_sso_token() to include a cryptographically-strong nonce (e.g.,
wp_generate_password) in the token payload and store a short-lived transient
keyed by that nonce (e.g., set_transient('wu_sso_token_' . $nonce, $user_id,
300)) when issuing the token, and then update
validate_sso_token_from_main_site() to extract the nonce from the token, verify
the transient exists via get_transient('wu_sso_token_' . $nonce), reject if
missing (used/expired), and delete the transient (delete_transient) on first
successful validation to enforce single-use.

---

Nitpick comments:
In `@inc/sso/class-sso.php`:
- Around line 653-664: The duplicate logic that pulls a return_url from a
redirect_to query string should be extracted into a private helper (e.g.,
extract_return_url_from_redirect($redirect_to)) and used from both
handle_already_logged_in_on_login_page() and handle_login_redirect(); create
extract_return_url_from_redirect to accept the $redirect_to string, run
wp_parse_url + parse_str and return the return_url if present (or null/empty),
then replace the duplicated block in the code snippet (and the similar block at
lines 569–577 inside handle_login_redirect()) with a call to this new method to
centralize behavior and avoid duplication.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1292b83f-cbfe-44f3-a1f3-0d3fe3fd0446

📥 Commits

Reviewing files that changed from the base of the PR and between d0a369e and 58e3467.

📒 Files selected for processing (1)
  • inc/sso/class-sso.php

Comment thread inc/sso/class-sso.php

// Check if this is an SSO flow (return_url param present)
// Check if this is an SSO flow (sso param or return_url param present)
$sso_action = $this->input('sso', '');

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Hardcoded 'sso' parameter name bypasses the wu_sso_get_url_path filter

Every other call site in this file retrieves the SSO URL path via $this->get_url_path(), which wraps apply_filters('wu_sso_get_url_path', 'sso', ...). Using a raw 'sso' literal here means that if wu_sso_get_url_path is filtered to a different value, $sso_action will always be empty and this branch of the SSO detection silently degrades to relying solely on return_url.

🐛 Proposed fix
-		$sso_action = $this->input('sso', '');
+		$sso_action = $this->input($this->get_url_path(), '');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$sso_action = $this->input('sso', '');
$sso_action = $this->input($this->get_url_path(), '');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@inc/sso/class-sso.php` at line 649, The code uses a hardcoded 'sso' literal
when calling $this->input which bypasses the wu_sso_get_url_path filter; change
the call that sets $sso_action to use $this->get_url_path() instead of the
literal so the filtered URL path is respected (i.e. replace $this->input('sso',
'') with $this->input($this->get_url_path(), '')); ensure this change is applied
wherever $sso_action is set so apply_filters('wu_sso_get_url_path', 'sso', ...)
can take effect.

Comment thread inc/sso/class-sso.php
if ( $parsed ) {
parse_str($parsed, $query_params);
if ( ! empty($query_params['return_url']) ) {
$return_url = $query_params['return_url'];

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing URL sanitization on the extracted return_url

$query_params['return_url'] is derived from user-controlled redirect_to input via parse_str but is assigned without any sanitization. Per coding guidelines, wu_clean() or a WordPress sanitization function must be used. For a value treated as a URL, esc_url_raw() is the right choice.

🛡️ Proposed fix
-					$return_url = $query_params['return_url'];
+					$return_url = esc_url_raw($query_params['return_url']);

As per coding guidelines: "Use wu_clean() or WordPress sanitization functions for input sanitization".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$return_url = $query_params['return_url'];
$return_url = esc_url_raw($query_params['return_url']);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@inc/sso/class-sso.php` at line 660, The extracted $query_params['return_url']
is assigned to $return_url without sanitization; update the code in the SSO
handling (where parse_str populates $query_params) to sanitize the value before
assignment by running it through a URL sanitizer (use esc_url_raw() per
guidelines or wu_clean() as an alternative) and ensure you check
isset($query_params['return_url']) and provide a safe fallback if missing/empty.

@superdav42 superdav42 merged commit ad6bc99 into main May 4, 2026
11 checks passed
@superdav42

Copy link
Copy Markdown
Collaborator Author

Summary

Enables SSO login from subsite to main site without requiring 3rd party cookies.

Problem

When visiting a subsite (e.g., vidanuevanaz.org) wp-admin, users were redirected to the main site (mygratis.site) login page. After logging in, the redirect back to the subsite didn't work without 3rd party cookies enabled.

Solution

  1. Token-based authentication: After login on main site, generate a time-limited wu_sso_token and add it to the return URL when redirecting to a different domain.
  2. Subsite token validation: The multi-tenancy plugin's handle_sso_login() now accepts simpler token format from the main site's SSO handler.
  3. Already-logged-in handling: When user is already logged in on main site and visits login page with SSO params, redirect directly to subsite with token instead of showing "already logged in" message.

Changes

  • class-sso.php: Add generate_sso_token(), handle_login_redirect() extracts return_url from query params, handle_already_logged_in_on_login_page() handles logged-in users on login page
  • class-remote-sso-handler.php (multi-tenancy): Add validate_sso_token_from_main_site() to accept simpler token format

Testing

  1. Visit vidanuevanaz.org/wp-admin/ (unauthenticated)
  2. Should redirect to mygratis.site/login/?sso=login&return_url=https://vidanuevanaz.org
  3. Log in with credentials
  4. Should redirect back to vidanuevanaz.org - logged in
    Also test already-logged-in flow:
  5. Already logged in on main site
  6. Visit subsite wp-admin
  7. Should redirect directly to subsite dashboard

aidevops.sh v3.14.51 plugin for OpenCode v1.14.33 with claude-sonnet-4-6 spent 15h 33m on this as a headless worker.


Merged via PR #1084 to main.
Merged by deterministic merge pass (pulse-wrapper.sh).

@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown

Performance Test Results

Performance test results for 9490114 are in 🛎️!

Note: the numbers in parentheses show the difference to the previous (baseline) test run. Differences below 2% or 0.5 in absolute values are not shown.

URL: /

Run DB Queries Memory Before Template Template WP Total LCP TTFB LCP - TTFB
0 41 37.80 MB 856.50 ms (+26.00 ms / +3% ) 150.50 ms (-3.50 ms / -2% ) 1064.00 ms (+74.00 ms / +7% ) 2082.00 ms (+120.00 ms / +6% ) 1983.25 ms (+110.60 ms / +6% ) 89.95 ms
1 56 49.10 MB 944.50 ms (+53.50 ms / +6% ) 140.00 ms 1085.00 ms (+54.00 ms / +5% ) 2084.00 ms (+66.00 ms / +3% ) 2003.25 ms (+67.65 ms / +3% ) 80.70 ms

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant