I have a subdirectory WordPress network with >50 sites, it’s on a primary domain of wordpress.example.com
.
A client, johndoe, within my network will (intentionally) have a back-end that includes the main site (for branding and ownership reasons):
wordpress.example.com/johndoe/wp-admin/
then via domain mapping with WordPress MU Domain Mapping By Donncha O Caoimh, a front-end of:
johndoe.com/
My problem is that the login cookie associates only to wordpress.example.com
and is completely unrelated and unaware of johndoe.com
, so the front-end does not recognize a user is actually logged in. This yields:
- no user tool-bar on front-end
- a posts “PREVIEW changes” button doesn’t work
is_user_logged_in()
doesn’t work in front-end- front-end page builder plugins won’t work
By disabling ‘remote login’ I know I can make the back-end of the site johndoe.com/wp-admin/
which would solve all aforementioned problems. However, keeping the primary domain for the back-end is crucial. In all my readings I haven’t found a solution, and I’ve let this question sit for years.
I know WordPress.com (itself powered by a WordPress network) seems to have solved this issue. When logged into wordpress.com venturing to a random wordpress.com blog like https://longitudes.ups.com I’m able to see my .com login, and the toolbar does not appear to be an iframe or anything silly done.
So, my question is, if the front-end domain of a WordPress site is different than the back-end, is there anyway to tie the login cookie to both? If the answer is “you can’t” (as all my research has returned), my follow up is, well how do the folks at Automattic do it?
2 Answers
WordPress is deciding whether you are logged in or not by checking AUTH_COOKIE
and LOGGED_IN_COOKIE
. As you have noticed these cookies are set in the same, let,s say, A
domain which your site is. Adding the same cookies to your second B
domain would make your user logged in in two A
and B
domains. Of course setting cookie from domain A
for second B
domain would be an enormous security flaw so you must send cookie values from domain A
to domain B
and set these cookies in domain B
.
So this is what we have to do:
- read cookies
AUTH_COOKIE
andLOGGED_IN_COOKIE
on domainA
- send cookies
AUTH_COOKIE
andLOGGED_IN_COOKIE
from domainA
to domainB
- set cookies
AUTH_COOKIE
andLOGGED_IN_COOKIE
on domainB
To read cookies we have to use two filtes set_auth_cookie
and set_logged_in_cookie
. To set cookies on domain B
users browser must be on site B
so we need to redirect user from domain A
to domain B
with cookie values. Redirecting with GET
params is not an option, cookies are security sensitive, we must use POST
request. To redirect user and send cookies data with POST we can create simple html form with url pointed to domain B
. After user is redirected we can set cookies on domain B
and turn back user to domain A
.
I created working code for my implementation.
/**
* DOMAIN A PART PLUGIN
*/
class WPSE_287556_Send_Cookies {
/**
* Domain which user have to be redirected
*
* @var array
*/
private $domainB = 'example.com';
/**
* Array of cookies to send
*
* @var array
*/
private $cookies = array();
/**
* WPSE_287556_Send_Cookies constructor.
*/
public function __construct()
{
/**
* Define plugin related hooks
*/
$this->define_hooks();
}
/**
* Save auth and logged in cookies to array
*/
public function save_cookie( $cookie, $expire, $expiration, $user_id, $scheme, $token ) {
$this->cookies[] = $data = array(
'cookie' => $cookie,
'expire' => $expire,
'scheme' => $scheme,
);
}
/**
* Display redirect post form
*
* We should not redirect user with cookies in get parameters because this is
* no safe. We also can not redirect user with post parameters. We can create
* html post form and submit it with js.
*/
public function display_redirect_form( $redirect_to, $requested_redirect_to, $user ) {
if( is_array( $this->cookies ) && !empty( $this->cookies ) ):
$url = ( is_ssl() ) ? 'https://' : 'http://' . $this->domainB . "https://wordpress.stackexchange.com/";
?>
<form action="<?php echo esc_url( $url ); ?>" method="post" style="display: none;" id="post_redirect_form">
<input type="hidden" name="action" value="set_cookies" >
<?php foreach($this->cookies as $index => $cookie): ?>
<input type="hidden" name="cookies[<?php esc_attr_e( $index ); ?>][cookie]" value="<?php esc_attr_e( $cookie['cookie'] ); ?>" >
<input type="hidden" name="cookies[<?php esc_attr_e( $index ); ?>][expire]" value="<?php esc_attr_e( $cookie['expire'] ); ?>" >
<input type="hidden" name="cookies[<?php esc_attr_e( $index ); ?>][scheme]" value="<?php esc_attr_e( $cookie['scheme'] ); ?>" >
<?php endforeach; ?>
<input type="hidden" name="redirect_to" value="<?php esc_attr_e( $redirect_to ); ?>" >
</form>
<script> document.getElementById('post_redirect_form').submit(); </script>
<?php exit; ?>
<?php endif;
return $redirect_to;
}
/**
* Define plugin related hooks
*/
private function define_hooks() {
/**
* Save cookies hook
*/
add_action( 'set_auth_cookie', array($this, 'save_cookie'), 10, 6 );
add_action( 'set_logged_in_cookie', array($this, 'save_cookie'), 10, 6 );
/**
* Display redirect post form
*
* This filter is used to modify redirect url after login. There is no
* better place to modify page content after user login. Additionally
* we have access to $redirect_to url which we can use later.
*/
add_filter('login_redirect', array( $this, 'display_redirect_form' ), 10, 3);
}
}
new WPSE_287556_Send_Cookies();
/**
* END OF DOMAIN A PART PLUGIN
*/
/**
* DOMAIN B PART PLUGIN
*/
class WPSE_287556_Set_Cookies {
/**
* WPSE_287556_Set_Cookies constructor.
*/
public function __construct()
{
/**
* Define plugin related hooks
*/
$this->define_hooks();
}
/**
* Set auth and logged in cookies
*/
public function set_cookies() {
// Check if request is "set auth cookie" request
if( $_SERVER['REQUEST_METHOD'] === 'POST' && isset( $_POST['action'] ) && $_POST['action'] === 'set_cookies' ) {
$args = array(
'redirect_to' => FILTER_SANITIZE_URL,
'cookies' => array(
'filter' => FILTER_SANITIZE_STRING,
'flags' => FILTER_REQUIRE_ARRAY,
),
);
// Read and filter all post params
$post = filter_input_array(INPUT_POST, $args);
$redirect_to = $post['redirect_to'];
$cookies = $post['cookies'];
foreach( $cookies as $cookie_params ){
$scheme = $cookie_params['scheme'];
$cookie = $cookie_params['cookie'];
$expire = (int) $cookie_params['expire'];
// Decide which cookie to set
switch( $scheme ) {
case 'logged_in':
// Set logged in cookie, most of the code is from wp_set_auth_cookie function
setcookie( LOGGED_IN_COOKIE, $cookie, $expire, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true);
if ( COOKIEPATH != SITECOOKIEPATH )
setcookie(LOGGED_IN_COOKIE, $cookie, $expire, SITECOOKIEPATH, COOKIE_DOMAIN, is_ssl(), true);
break;
case 'secure_auth':
case 'auth':
// Set auth cookie, most of the code is from wp_set_auth_cookie function
if ( $scheme === 'secure_auth' ) {
$auth_cookie_name = SECURE_AUTH_COOKIE;
} else {
$auth_cookie_name = AUTH_COOKIE;
}
setcookie($auth_cookie_name, $cookie, $expire, PLUGINS_COOKIE_PATH, COOKIE_DOMAIN, is_ssl(), true);
setcookie($auth_cookie_name, $cookie, $expire, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, is_ssl(), true);
break;
}
}
// Redirect user to previous site
header( 'Location: ' . esc_url( $redirect_to ) );
exit;
}
}
/**
* Define plugin related hooks
*/
private function define_hooks() {
/**
* Set cookies from request
*/
add_action( 'init', array($this, 'set_cookies'));
}
}
new WPSE_287556_Set_Cookies();
/**
* END OF DOMAIN B PART PLUGIN
*/