diff --git a/assets/js/sso.js b/assets/js/sso.js
index 492b0b648..14a2fb6b5 100644
--- a/assets/js/sso.js
+++ b/assets/js/sso.js
@@ -21,6 +21,11 @@
const denied = wu_read_cookie('wu_sso_denied');
+ const checkout_form = document.querySelector(
+ '#wrapper-field-checkout, .wu-checkout, .wu-checkout-form'
+ );
+ const checkout_url = /\/register\/?$/.test(window.location.pathname);
+
document.head.insertAdjacentHTML('beforeend', `
`);
- if (! o.is_user_logged_in && ! denied) {
+ if (! o.is_user_logged_in && ! denied && ! checkout_form && ! checkout_url) {
const s = document.getElementsByTagName('script')[ 0 ];
diff --git a/inc/sso/class-sso.php b/inc/sso/class-sso.php
index 63ec842ce..cbbff0894 100644
--- a/inc/sso/class-sso.php
+++ b/inc/sso/class-sso.php
@@ -977,13 +977,87 @@ public function handle_broker($response_type = 'redirect'): void {
// Attach through redirect if the client isn't attached yet.
if ( ! $broker->isAttached()) {
+ $sso_path = $this->get_url_path();
+
+ /*
+ * Determine the page we ultimately want the user to land on
+ * after the magic-link round-trip.
+ *
+ * The JSONP must-redirect fallback navigates the top-level
+ * browser to `/sso?return_url=`, so on
+ * that second hit `get_current_url()` is the /sso URL itself
+ * — using it would feed `/sso?...` back into return_url and
+ * the main site would echo a longer `/sso?return_url=/sso?...`
+ * URL on each iteration, producing a `ERR_TOO_MANY_REDIRECTS`
+ * (the URL grows on every hop, never converges).
+ *
+ * Prefer the explicit `return_url` query param when present
+ * (set by sso.js on the must-redirect branch); only fall back
+ * to `get_current_url()` on a direct first hit. Guard the
+ * value against pointing at the /sso path itself so a
+ * stray/legacy URL can't restart the same loop.
+ */
+ $requested_return = (string) $this->input('return_url', '');
+
+ if ( '' === $requested_return ) {
+ $return_url = $this->get_current_url();
+ } else {
+ $return_url = $requested_return;
+ }
+
+ $return_path = (string) wp_parse_url($return_url, PHP_URL_PATH);
+
+ if ( '' === $return_path || preg_match("#/{$sso_path}(-grant)?/?$#", $return_path) ) {
+ // Refuses to round-trip the /sso endpoint itself; fall back to subsite home.
+ $return_url = home_url('/');
+ }
+
+ /*
+ * Front-end auto-SSO target: send the user to the main site's
+ * /sso endpoint with a return_url + redirect_to. handle_server()
+ * on the main site checks the main-site auth cookie (first-party
+ * at that point — no third-party cookie restrictions apply) and,
+ * if logged in, issues an HMAC-signed `wu_sso_token` magic-link
+ * back to this subsite. handle_cookie_less_sso_token() consumes
+ * it on init and sets the broker-side auth cookie.
+ *
+ * The legacy `$broker->getAttachUrl()` returned a /sso-grant URL
+ * whose handler (`wu_sso_handle_sso_grant_grant`) is unreachable
+ * under the cookie-less rework introduced in #1084, so the old
+ * attach handshake silently fell through to a WordPress 404 and
+ * SSO never completed. Using /sso directly hits the cookie-less
+ * server path and works in any browser that allows the main-site
+ * first-party auth cookie.
+ *
+ * Pass `redirect_to` explicitly so main's `get_sso_redirect_to()`
+ * does not append `/wp/wp-admin/` (its fallback when only a
+ * cross-domain return_url is supplied), which would land the
+ * user on the subsite admin instead of the page they were
+ * actually viewing.
+ */
+ $main_sso_url = add_query_arg(
+ [
+ $sso_path => 'login',
+ 'return_url' => $return_url,
+ 'redirect_to' => $return_url,
+ ],
+ get_home_url(wu_get_main_site_id(), $sso_path)
+ );
+
/*
- * For JSONP requests (initiated by a