Skip to content

Commit 7060ede

Browse files
bourafaiclaude
andcommitted
Fix magic login behind CDNs that strip Set-Cookie headers
CDNs configured to cache aggressively (e.g. Cloudflare with "Cache Everything") strip Set-Cookie headers from the magic login response, preventing users from being authenticated. Use a two-step handoff: the magic URL now creates a short-lived transient and redirects to wp-login.php, which CDNs universally exclude from caching. The auth cookies are set there instead. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6aabb74 commit 7060ede

File tree

1 file changed

+55
-84
lines changed

1 file changed

+55
-84
lines changed

plugin/wp-cli-login-server.php

Lines changed: 55 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,48 @@ function init_server_from_request()
2626
add_action('plugins_loaded', __NAMESPACE__ . '\\init_server_from_request');
2727
}
2828

29+
/**
30+
* Handle the second leg of the magic login on wp-login.php.
31+
*
32+
* CDN/proxy layers (e.g. Cloudflare with "Cache Everything") strip Set-Cookie
33+
* headers from responses on arbitrary URLs. wp-login.php is universally excluded
34+
* from CDN caching, so setting cookies here guarantees they reach the browser.
35+
*/
36+
function handle_login_handoff()
37+
{
38+
if (empty($_GET['wp_cli_login_token'])) {
39+
return;
40+
}
41+
42+
$token = sanitize_text_field($_GET['wp_cli_login_token']);
43+
$data = get_transient('wp_cli_login/handoff/' . $token);
44+
delete_transient('wp_cli_login/handoff/' . $token);
45+
46+
if (! $data || ! ($user = new \WP_User($data['user_id'])) || ! $user->exists()) {
47+
wp_die('The magic login handoff has expired or is invalid.', '', ['response' => 410]);
48+
}
49+
50+
wp_set_auth_cookie($user->ID);
51+
do_action('wp_cli_login/login', $user->user_login, $user);
52+
do_action('wp_login', $user->user_login, $user);
53+
54+
$redirect_to = $data['redirect_url'] ?: admin_url();
55+
56+
if ((empty($redirect_to) || $redirect_to == 'wp-admin/' || $redirect_to == admin_url())) {
57+
if (is_multisite() && ! get_active_blog_for_user($user->ID) && ! is_super_admin($user->ID)) {
58+
$redirect_to = user_admin_url();
59+
} elseif (is_multisite() && ! $user->has_cap('read')) {
60+
$redirect_to = get_dashboard_url($user->ID);
61+
} elseif (! $user->has_cap('edit_posts')) {
62+
$redirect_to = $user->has_cap('read') ? admin_url('profile.php') : home_url();
63+
}
64+
}
65+
66+
wp_safe_redirect($redirect_to);
67+
exit;
68+
}
69+
add_action('login_init', __NAMESPACE__ . '\\handle_login_handoff');
70+
2971
/**
3072
* @return bool
3173
*/
@@ -127,14 +169,25 @@ public function checkEndpoint()
127169

128170
/**
129171
* Attempt the magic login.
172+
*
173+
* Rather than setting auth cookies directly (which CDN/proxy layers like
174+
* Cloudflare may strip), hand off to wp-login.php via a short-lived token.
130175
*/
131176
public function run()
132177
{
133178
try {
134179
$magic = $this->loadMagic();
135180
$user = $this->validate($magic);
136-
$this->loginUser($user);
137-
$this->loginRedirect($user, $magic->redirect_url);
181+
$this->deleteMagic();
182+
183+
$token = bin2hex(random_bytes(16));
184+
set_transient('wp_cli_login/handoff/' . $token, [
185+
'user_id' => $user->ID,
186+
'redirect_url' => $magic->redirect_url,
187+
], 30);
188+
189+
wp_redirect(add_query_arg('wp_cli_login_token', $token, wp_login_url()));
190+
exit;
138191
} catch (Exception $e) {
139192
$this->deleteMagic();
140193
$this->abort($e);
@@ -198,88 +251,6 @@ private function deleteMagic()
198251
delete_transient($this->magicKey());
199252
}
200253

201-
/**
202-
* Login the given user and redirect them to wp-admin.
203-
*
204-
* @param WP_User $user
205-
*/
206-
private function loginUser(WP_User $user)
207-
{
208-
$this->deleteMagic();
209-
210-
wp_set_auth_cookie($user->ID);
211-
212-
/**
213-
* Fires after the user has successfully logged in via the WP-CLI Login Server.
214-
*
215-
* @param string $user_login Username.
216-
* @param WP_User $user WP_User object of the logged-in user.
217-
*/
218-
do_action('wp_cli_login/login', $user->user_login, $user);
219-
220-
/**
221-
* Fires after the user has successfully logged in.
222-
*
223-
* @param string $user_login Username.
224-
* @param WP_User $user WP_User object of the logged-in user.
225-
*/
226-
do_action('wp_login', $user->user_login, $user);
227-
}
228-
229-
/**
230-
* Redirect the user after logging in.
231-
*
232-
* Mostly copied from wp-login.php
233-
*
234-
* @param WP_User $user
235-
* @param string $redirect_url
236-
*/
237-
private function loginRedirect(WP_User $user, $redirect_url)
238-
{
239-
$redirect_to = $redirect_url ?: admin_url();
240-
241-
/**
242-
* Filters the login redirect URL.
243-
*
244-
* @param string $redirect_to The redirect destination URL.
245-
* @param string $requested_redirect_to The requested redirect destination URL passed as a parameter.
246-
* @param WP_User $user WP_User object.
247-
*/
248-
$redirect_to = apply_filters('login_redirect', $redirect_to, '', $user);
249-
250-
/**
251-
* Filters the login redirect URL for WP-CLI Login Server requests.
252-
*
253-
* @param string $redirect_to The redirect destination URL.
254-
* @param string $requested_redirect_to The requested redirect destination URL passed as a parameter.
255-
* @param WP_User $user WP_User object.
256-
*/
257-
$redirect_to = apply_filters('wp_cli_login/login_redirect', $redirect_to, '', $user);
258-
259-
/**
260-
* Figure out where to redirect the user for the default wp-admin URL based on the user's capabilities.
261-
*/
262-
if ((empty($redirect_to) || $redirect_to == 'wp-admin/' || $redirect_to == admin_url())) {
263-
// If the user doesn't belong to a blog, send them to user admin. If the user can't edit posts, send them to their profile.
264-
if (is_multisite() && ! get_active_blog_for_user($user->ID) && ! is_super_admin($user->ID)) {
265-
$redirect_to = user_admin_url();
266-
} elseif (is_multisite() && ! $user->has_cap('read')) {
267-
$redirect_to = get_dashboard_url($user->ID);
268-
} elseif (! $user->has_cap('edit_posts')) {
269-
$redirect_to = $user->has_cap('read') ? admin_url('profile.php') : home_url();
270-
}
271-
272-
wp_redirect($redirect_to);
273-
exit;
274-
}
275-
276-
/**
277-
* Redirect safely to the URL provided.
278-
*/
279-
wp_safe_redirect($redirect_to);
280-
exit;
281-
}
282-
283254
/**
284255
* Abort the process; Explode with terrifying message.
285256
*

0 commit comments

Comments
 (0)