Skip to content

Cross Domain Cookie Sharing for WordPress

by John on March 4th, 2011

It could just be me, but I feel as though I’ve seen a lot of emphasis lately on simplifying and consolidating logins actions. One of the things I’ve been considering, particularly for WordPress, is Cross-Domain Authentication and Cookie Sharing. I took a couple of days to see if it was possible, and as it turns out, it is!

Here’s how I did it.

Pre-Requisites

The first order of business is to ensure all the sites being shared are using the same database and users tables. To do this, make sure each site points to the same database and add these to wp-config.php:

define('MASTER_DOMAIN', 'domain.com' ); // Change this to the domain name of your master site
define('CAP_PREFIX', 'wp_'); // Change this prefix of your master site
define('CUSTOM_USER_TABLE', CAP_PREFIX.'users');
define(‘CUSTOM_USER_META_TABLE’, CAP_PREFIX.’usermeta’);

You could also make this hack in “/wp-includes/capabilities.php”:

CHANGE THIS:

$this->cap_key = $wpdb->prefix . 'capabilities';

TO THIS:

$this->cap_key = defined('CAP_PREFIX') ? CAP_PREFIX . 'capabilities' : $wpdb->prefix . 'capabilities';

Cross Domain Authentication works by defining a central domain (which we will call the master), to store a cookie for each user session. Any of the other domains (we will call them slaves) will either get or send the cookie to the master.

First – store the master and current base urls so we have easy access to them:

$wpcl_master_domain = 'http://'. MASTER_DOMAIN;
$wpcl_current_domain = 'http://'.$_SERVER["SERVER_NAME"];

We call it the current domain because it’s possible that the user might be logged onto the master domain.

On to the actual actions. Firstly, when content on a slave domain is loaded, there are two possible scenarios.

Scenario 1:

The slave does not possess an authenticated cookie. In this case we want to check with the master and create a slave cookie if possible.

  • Slave sends a request to the master that asks for a cookie value
  • Master sends a request to the slave that provides a cookie value
  • Slave authenticates the cookie value and sets slave cookie(s)

Scenario 2:

The slave does posess an authenticated cookie. In this case we want to notify the master and create a master cookie if possible.

  • Slave sends a request to the master that provides a cookie value
  • Master authenticates the cookie value and sets master cookie(s)

You’ll notice that in both scenarios, there are potentially three actions.

Stage 1, Requesting a Cookie Value:

This is only ever done by a slave to a master, so we don’t need any parameters for the function. We’re also providing information about where to send the cookie back to.

function wpcl_getUrlForCookieRequest(){
	global $wpcl_current_domain, $wpcl_master_domain;
	$target_url = add_query_arg('wpcl-caller', urlencode($wpcl_current_domain), $wpcl_master_domain);
	$target_url = add_query_arg('wpcl-stage', '1', $target_url);
	return $target_url;
}

Stage 2, Sending a Cookie Value:

This could be done by both the slave and the master, so we have an option of the URL to start with. We’re sending the logged-in cookie from the site as a value in the URL.

function wpcl_getUrlToSendCookie($target_url){
	$target_url = add_query_arg('wpcl-logged-in', urlencode($_COOKIE[LOGGED_IN_COOKIE]), $target_url);
	$target_url = add_query_arg('wpcl-stage', '2', $target_url);
	return $target_url;
}

Stage 3, Receiving, authenticating, and setting cookies:

This is super easy with WordPress’ built in functions:

function wpcl_receive_cookie(){
	$userId = wp_validate_auth_cookie(urldecode($_GET['wpcl-logged-in']), 'logged_in');
	if ( $userId === false ) { return; }
	wp_set_auth_cookie($userId);
}

Stage 4, Clear the parameters we were using (Optional):

This is not necessary if we’re doing things in the background (read on), but if you have the need, here’s how to do it:

function wpcl_clear_params() {
	global $wpcl_current_domain;
	$target_url = $wpcl_current_domain .$_SERVER["REQUEST_URI"];
	$target_url = remove_query_arg('wpcl-caller', $target_url);
	$target_url = remove_query_arg('wpcl-logged-in', $target_url);
	$target_url = remove_query_arg('wpcl-stage', $target_url);
	wp_redirect($target_url);
    exit;
}

Let’s move on to the action logic. With the ability to send and receive cookies out of the way, we need to set up some handling methods. The following code monitors the ‘wpcl-stage’ parameter in the URL to determine if any action needs to be carried out. As you can see, if it notices we’re in Stage 1 it will respond with Stage 2. If it notices we’re in Stage 2 it will respond with Stage 3. We want this to happen as early as possible in the WP Lifecycle, as to not waste resources. So we hook it into ‘init’.

function wpcl_action() {
	$wpcl_stage = $_GET['wpcl-stage'];
    switch ($wpcl_stage) {
        case '1':
            $target_url = wpcl_getUrlToSendCookie(urldecode($_GET['wpcl-caller']));
			wp_redirect($target_url);
			exit;
            break;
        case '2':
            wpcl_receive_cookie();
            break;
        default:
			break;
    }
}
add_filter('init', 'wpcl_action', 10, 2);

Almost there; we just need to initialize the entire process. I’ve chosen to use an image link to do this in the background. But, it could be done other ways too. There are some caveats to this method – in particular, doing it in the footer requires a full page load before requesting the cookie. So, a user might not be authenticated until the page refreshes a second time. Nonetheless, this shows one method of how the process can be done in the background.

function wpcl_background() {
	global $wpcl_master_domain, $wpcl_current_domain;

	if ( $wpcl_current_domain == $wpcl_master_domain ) { return; } // Bypass for master

	if ( is_user_logged_in() ) {
		// If logged in on a slave site, send the cookie to the master
		$target_url = wpcl_getUrlToSendCookie($wpcl_master_domain);
	} else {
		// If not logged in on a slave site, request a cookie from the master
		$target_url = wpcl_getUrlForCookieRequest();
	}
	?><img src="<?php echo $target_url; ?>" alt="" style="display=none;"; /><?php
}
add_action('wp_footer', 'wpcl_background');
add_action('admin_footer', 'wpcl_background');

Finally, just make sure this code is loaded and active (as a plugin) in the master domain and all slave domains.

Using this technique, one could potentially enable an infinite number of WordPress sites to share login sessions by utilizing cross-domain authentication and cookie sharing.

View the full code here.

This has been marginally tested and found to be working on both WP Multisite and WP Hive. It comes with no guarantee and no support. If you find it to be useful, please share it with others.

From → Development

8 Comments
  1. dave permalink

    Great plugin! But, what if the master domain is any of number of blogs? For example, I have 10 blogs in my network. If a user logs into ANY one, I want him/her automatically logged into them all, ATM, your plugin does not support that, any way of modifying your plugin to support it?

    Thanks!

  2. Very good ,这个可以有~~~~~

  3. Jason permalink

    Hi,
    Thanks, this has been very helpful.

    One note, this line
    define(‘CUSTOM_USER_META_TABLE’, CAP_PREFIX.’wp_usermeta’);

    should be this:

    define(‘CUSTOM_USER_META_TABLE’, CAP_PREFIX.’usermeta’);

  4. mucky permalink

    In which files are these functions?

    The link to “View the full code here.” is broken.

    Thanks a lot

  5. WH0RU permalink

    How to use this thing?? I mean where to put your file wp-central-login.php??

  6. Larry permalink

    Do you know if this will work on a mapped domain? It doesn’t appear to based on my implementation of it…

  7. Am I missing something, or are you sending the naked cookie value in the query string? This seems terribly insecure.

  8. You’re missing a closing “;” in the first step at “define(‘CAP_PREFIX’, ‘wp_’)”

    Will break the entire WordPress site without it.

Leave a Reply

Note: XHTML is allowed. Your email address will never be published.

Subscribe to this comment feed via RSS