Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

sharing user on 2 WordPress installs in 2 sub domains on same server

I have 2 WordPress sites in 2 different sub-domains like test1.abc.com and test2.abc.com . Both sites have wp-require plugin activated and only logged-in users can see the site. We want to make a system where if a user logged into one site, he should be auto-login into the other one.

What i try :

After some searching I know I need to use one database for both sites. So I have done these steps:

I have download the whole database of test2.abc.com site and change all prefix wp_ to wpmo_, replaced it in whole database and upload it into first site's database. I added these 2 lines in wp-config.php of the second site, to define that second site should use first site's user table not its own.

define('CUSTOM_USERMETA_TABLE', 'wp_usermeta');
define('CUSTOM_USER_TABLE', 'wp_users');

Now, the second site is using the first site's users and I am able to login to the second site by the user details of first site.

The next problem is cookies, so I added these lines in wp-config of both sites.

define('COOKIE_DOMAIN', '.abc.com');
define('COOKIEPATH', '/');
define('COOKIEHASH', 'aee53c017c29dc0d3ae37253fc8cbfd8');

Now I logged in into test1.abc.com and when I go to test2.abc.com , it asks me to login. That means cookies are not passing from the first site to the second one. However, i tried to be print $_COOKIE and it gives me same encrypt values, but user still not auto login on second site. also when i came back to first site , it automatic logout. i feel like both sites are related somehow on cookies and i am close but still not reached to my goal of auto login into second site.

Any help?

Solution :

After some help from Mikk3lRo and others, i have managed to solve this issue. i am posting the solution for anyone who faces the same problem. here you can find step by step guide for this :

step 1: use one database for both installations, install 2 wp by using 2 different prefix on installation time.

step 2: Make sure that the randomly generated secret keys and salts are also identical in both wp-config.php files.

step 3: paste these 2 lines in wp-config.php of second site.

//Share user tables
define('CUSTOM_USER_META_TABLE', 'SITE1_PREFIX_usermeta');
define('CUSTOM_USER_TABLE', 'SITE1_PREFIX_users');

step 4: share cookies with these lines . (write in both wp-config.php)

//Share cookies
define('COOKIE_DOMAIN', '.abc.com');
define('COOKIEHASH', 'aee53c017c29dc0d3ae37253fc8cbfd8');

step 5: now you will be able to auto login in second site when logged into first site. but your will an error message on second site "you do not have permission to access this page", which is a good thing.

step 6: The reason is, WordPress checks the user capability (wp-includes/capabilities.php) so either you have directly add this capability in database (in case you only have few users) or to write a plugin for this. @Mikk3lRo writes a plugin for this in comments, which is Good.

Thanks

like image 351
Gaurav Avatar asked Jun 20 '15 10:06

Gaurav


People also ask

Do I need to install WordPress on each subdomain?

Because subdomains are separate domains, it can be harder to integrate them into the rest of your site. For example, if you're using a single site WordPress install, you'd need two separate installs — one for your main domain name and one for the subdomain.

How to share logins and users between multiple WordPress sites?

Log into the second installation admin panel as admin and list WordPress users. You'll find the new admin user and all users from the first website (this allows them to share logins). At this point, users from one site won't be able to log into the other website.

Can two WordPress sites share a database?

Can two WordPress websites share the same database? Yes, you can set up two or more WordPress sites using only one database. There are a couple of ways to set this up. First, you can set up a single WordPress instance for two sites using the WordPress Multisite feature.

Can you have two sub domains?

Each domain name can have up to 500 subdomains. You can also add multiple levels of subdomains, such as info.blog.yoursite.com. A subdomain can be up to 255 characters long, but if you have multiple levels in your subdomain, each level can only be 63 characters long.


2 Answers

Alright - you are very close, but there are a few more things to be done.

All requirements are as follows:

  • Share the same database, using different prefixes - you've done this. From here I assume the prefixes are wp1_, wp2_ and so on.
  • Share the wp1_users and wp1_usermeta tables - you've done this - and actually you would have overcome this obstacle if only you had spelled the constants name correctly... it's CUSTOM_USER_META_TABLE (one more underscore than what you have)
  • Share cookies between the subdomains using a common COOKIE_DOMAIN and COOKIEHASH - you've done this
  • Make sure that the (normally) randomly generated secret keys and salts are also identical - you don't write that you've done this, but judging by your results I think either you have or your keys are empty (which is not good, but will work)
  • Make sure there's a prefix_capabilities entry for each user for each site in the shared usermeta table - I don't think you've done this, simply because you haven't yet reached the point where you realize it's necessary.

Complete solution:

This goes in wp-config.php:

//Share user tables
define('CUSTOM_USER_META_TABLE', 'wp1_usermeta');
define('CUSTOM_USER_TABLE', 'wp1_users');

//Share cookies
define('COOKIE_DOMAIN', '.abc.com');
define('COOKIEHASH', 'aee53c017c29dc0d3ae37253fc8cbfd8');

/**
 * In my case these are not needed - but they may well be if one
 * of the installs is located in a sub-folder. I have not tested
 * this theory though.
 */
//define('COOKIEPATH', '/');
//define('SITECOOKIEPATH', '/');
//define('ADMIN_COOKIE_PATH', '/wp-admin');

//These all need to be identical
define('AUTH_KEY', 'this should be random');
define('SECURE_AUTH_KEY', 'this should also be a random string');
define('LOGGED_IN_KEY', 'one more random string');
define('AUTH_SALT', 'oh my - so many random strings');
define('SECURE_AUTH_SALT', 'salt, salt, salt and no eggs');
define('LOGGED_IN_SALT', 'this is sooooo random');

/**
 * These do not need to be shared - in fact they probably shouldn't be
 * - if they are you could (in theory) do actions on one site that was
 * intended for the other - probably not a very big concern in reality
 */
define('NONCE_KEY', 'these should be random too, but can differ');
define('NONCE_SALT', 'one site has one, the other another');

This is enough to get you logged in on both sites - but there's still that last annoying bullet left on the list.

The problem is that your permissions ("capabilities") are only good on one of the sites because the meta_key is prefixed with the table prefix of the site. If you google around a bit you will find lots of solutions recommending to modify wp-includes/capabilities.php to just use a common prefix instead - I strongly recommend against that! (not for security reasons, but because you will need to make this patch / hack after every update... and it's just insanely bad practice to modify core files)

Instead to remedy this obstacle you need to duplicate the wp1_capabilities row in the wp1_usermeta table (for each user!), giving it a new umeta_id and substituting the table prefix wp1_ with wp2_ in the meta_key column. You need to do this for each site, so you have one row with meta_key wp1_capabilities, one with wp2_capabilities and so on.

If you and a friend of yours are the only users who'll ever log in to the sites, then just do it by hand through phpMyAdmin or something - if you need it to work dynamically, then it should be quite possible to automate with a small plugin (see edit below).

I've always hated this design - a table prefix has no business inside a table row! I think it is needed for multisite installs, though I'm sure there would be other (better) ways to solve it...

Update: Plugin to keep user roles synchronized between all sites

This simple plugin will duplicate and keep the required rows in the usermeta table updated when users are created or edited.

One thing worth noting is that it probably won't work with multisite installs because they have some special capabilities / roles. I have not tested this.

It may need refinement for specific use cases (please do comment), but it does the job fine for my limited test case that only includes a few users. It will be inefficient for a site with thousands of users, but as it only runs when a user is modified, and only does updates if they are needed I doubt this will be a major concern. However it should be relatively easy to adapt to only read and modify the user that was just added / edited. It would complicate initial setup a bit though, as pre-existing users would not automatically get duplicated on the first run.

Create the folder wp-content/plugins/duplicate-caps and inside put the following in duplicate-caps.php - and don't forget to activate under plugins in wordpress admin. It needs to be installed on all sites.

<?php
/*
Plugin Name: Duplicate Caps
Plugin URI: 
Description: Tiny plugin to duplicate capabilities in a setup where users (and user tables) are shared across more than one install
Author: Mikk3lRo
Version: 0.1
Author URI: 
*/
$dummy = new duplicate_caps();
class duplicate_caps {
    function __construct() {
        add_action('updated_user_meta', array($this, 'update_prefixed_caps'), 10, 2);
        add_action('added_user_meta', array($this, 'update_prefixed_caps'), 10, 2);
        add_action('deleted_user_meta', array($this, 'update_prefixed_caps'), 10, 2);
    }
    function update_prefixed_caps($mid, $object_id) {
        /**
         * Note that $object_id contains the id of the user that was
         * just changed.
         * On a site with many users it would make sense to only
         * get and set information regarding the just-changed user
         * Currently this function corrects roles for all users
         * making sure pre-existing users are duplicated, and keeping
         * the table in sync.
         */
        global $wpdb;
        //Quick and dirty - get all *capabilities rows for all users
        $sql = "SELECT * FROM {$wpdb->usermeta} WHERE `meta_key` LIKE '%capabilities'";
        $results = $wpdb->get_results($sql) or die(mysql_error());

        //Will hold all prefixes (always include our own)
        $prefixes = array($wpdb->prefix);

        //Will grab the existing role for each prefix
        $user_roles = array();

        //Loop our results
        foreach ($results as $result) {
            //Make sure the meta_key looks right, and grab the prefix
            if (preg_match('#^(.*)capabilities$#', $result->meta_key, $matches)) {
                $prefix = $matches[1];

                // Collect prefixes
                $prefixes[] = $prefix;

                //Note the entire row for later use
                $user_roles[$result->user_id][$prefix] = $result;
            }
        }

        //Make sure we only have one of each
        $prefixes = array_unique($prefixes);

        //Loop through the users we found
        foreach ($user_roles as $user_id => $existing_prefixes) {
            if (!isset($existing_prefixes[$wpdb->prefix])) {
                //User was probably just deleted - all rows are deleted by
                //wordpress though, so no cleanup for us :)
            } else {
                //We want all prefixes to obey us (we just created or changed
                //the user, so we want that to affect all sites)
                $desired_role = $existing_prefixes[$wpdb->prefix]->meta_value;

                //Loop through all prefixes
                foreach ($prefixes as $prefix) {
                    //Data to be inserted / updated
                    $cap_data = array(
                        'user_id' => $user_id,
                        'meta_key' => $prefix . 'capabilities',
                        'meta_value' => $desired_role
                    );

                    //If the prefix doesn't exist for this user
                    if (!in_array($prefix, array_keys($existing_prefixes))) {
                        //Actually insert it (user was probably just created)
                        $wpdb->insert($wpdb->usermeta, $cap_data, array('%d', '%s', '%s'));
                    } else if ($desired_role !== $existing_prefixes[$prefix]->meta_value) {
                        //Update it if not already correct (user was probably just edited)
                        $cap_data['umeta_id'] = $existing_prefixes[$prefix]->umeta_id;
                        $wpdb->replace($wpdb->usermeta, $cap_data, array('%d', '%s', '%s', '%d'));
                    }
                }
            }
        }
    }
}
like image 74
Mikk3lRo Avatar answered Sep 28 '22 08:09

Mikk3lRo


I believe the easiest solution for you would be to make use of one of WordPress' single-sign on (SSO) plugins.

There's a large amount of plugins listed here.

You could use one of them or base your authentication on one of them.

Alternately there's multi-site which will let you create a network of sites, if you decide to create a multi-site, then please read this first.

like image 30
deepy Avatar answered Sep 28 '22 08:09

deepy