How to do single sign-on with PHP? [closed]

Solution 1:

Your question is too unspecific to give a precise answer. If you're trying to let users log in to your website using Google accounts, it's documented here.

On the other hand, if you're trying to let your users sign in to several websites you control with one account, here's how you can do it:

Make all login links on your sites point to a centralized login page, but include information about where the user came from in the link. For example:

<a href='http://login.example.com/login.php?source=my.other.site.com/foo/bar'>log in!!</a>

Then, once the user has logged in successfully, you redirect the user back to the original site while passing along whatever information you need about the authenticated user.

However, you also need to make sure that people can't just circumvent your authentication mechanism by adding the necessary authentication parameters to the URL. This can be done by including a signature in the form of an HMAC-SHA-256 of the parameters plus a secret that's stored on both login server and the originating site. (Preferably this key should be different for each site using your SSO system.)

<?php
$MySecretKey = 'Nobody Will Ever Guess This!!';

// Generate signature from authentication info + secret key
$sig = hash(
    'sha256',
     $user->id . $user->email,
     $MySecretKey
);

// Make sure we're redirecting somewhere safe
$source = parse_url($_GET['source']);
if(in_array($source->host, $list_of_safe_hosts))
  $target = 'http://'.$source->host.$source->path;

// Send the authenticated user back to the originating site
header('Location: '.$target.'?'.
    'user_id='.$user->id.
    '&user_email='.urlencode($user->email).
    '&sig='.$sig);
?>

Then, in the originating site, if the signature matches the user is already logged in. Store the info about the logged in user in session variables (not a cookie):

<?php
$MySecretKey = 'Nobody Will Ever Guess This!!';

// Set not logged in by default
$user_id = 0;
$user_email = '';

if(intval($_GET['user_id']) && !$_SESSION['user_id']) // Someone trying to log in?
{
  // See if they have the right signature
  if (hash_equals(hash('sha256', intval($_GET['user_id']).$_GET['user_email'], $MySecretKey), $sig)) {
    $_SESSION['user_id'] = intval($_GET['user_id']);
    $_SESSION['user_email'] = $_GET['user_email'];
  }
}

?>

Note that I'm using a function added in PHP 5.6: hash_equals. If you're on lower than 5.6, you can use this substitute function which implements a timing-safe comparison function using double HMAC verification:

function hash_equals($a, $b) {
    $key = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
    return hash_hmac('sha512', $a, $key) === hash_hmac('sha512', $b, $key);
}

This is obviously a very crude implementation, but it should be a decent starting point.

Solution 2:

If you are wanting a cross-site central login system, I would recommend something like OpenID. You could run your own server, and then have only one login for multiple sites.