How to Do Single Sign-On with PHP

How to do single sign-on with PHP?

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.

Steps to implement SSO for php application

who is the IDP and who will be the service provider.

IDP (Identity Provider) is the one who creates, stores, maintains and authenticates the identity of the user or principal in saml terms. So in your case it is the clients application.

SP (Service Provider) is the one who provides the service or resource to a user (authenticated by IDP) so in your case it is your application.

SSO workflow

could someone please help by mentioning the things that need to be implemented.

As you can see in the above diagram when user will try to access a resource on your site you will have to redirect them to IDP to confirm whether this user is authenticated and if you should return them the resource/response they are looking for. The SSO url and other details are exchanged between IDP and SP through Metadata.

Once IDP has authenticated the user it will POST a response on your application url. This response contains an assertion through which you will know user details and whether user is authenticated or not. You will have to parse this response (xml). Also, these assertions are generally signed with certificate and are encoded base 64.

You will also have to think about SLO so when a user clicks on logout in your site you might have to clear their session from your application and redirect them to the IDP so they get logged out from there as well.

As suggested by smartin you can use some library which will make it easier to implement SAML. I am also learning about SAML as we are working on converting our current application into IDP :)

I found this SAML official documentation and some of the diagrams very helpful. http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0-cd-02.html

Use of SSO in php

So, this is what the reality of the matter is.

OAuth allows you (a consumer in this case) to authenticate users via an OAuth provider (domain2.com in this case) without having the users supply their credentials to you (because they don't really trust you for whatever reason) but rather want to sign in domain2.com whom they trust.

To do this in the forward (traditional) direction, you provide your users an option to sign in with domain2.com which will redirect the user to domain2.com . This request will need to be made using your applications' key and secret to generate the signature (PHP OAuth does this for you). The user is then redirected to domain2.com to sign in and allow access to your domain. Then the user will be redirected to the callback you supply to domain2.com along with an access token which you can then use to make additional requests to domain2.com on the users behalf (if any) which in your case would just be a single request to verify that the token works which means it's authentic.

To do this in the reverse direction (as you described), domain2.com do the same more or less, use your key and secret (which it knows) to generate a user token and redirect the user to the URL you provide them so you can get the token and use it as a means to verify the user is indeed autheticated with domain2.com . To do only this, (which based on the question is probably what you need) you just need to let the people of domain2.com know which URL will accept their token and not much else. The rest is more or less the responsibility of domain2.com to do.

The whole purpose of the table http://bshaffer.github.io/oauth2-server-php-docs/cookbook/ is to store the user's token so you wouldn't need to have them authenticate on domain2.com every time, however it's optional and for your use case simply storing the token in the session may be enough.

How can I implement single sign-on (SSO) using Microsoft AD for an internal PHP app?

All you need is the mod_auth_sspi Apache module.

Sample configuration:

AuthType SSPI
SSPIAuth On
SSPIAuthoritative On
SSPIDomain mydomain

# Set this if you want to allow access with clients that do not support NTLM, or via proxy from outside. Don't forget to require SSL in this case!
SSPIOfferBasic On

# Set this if you have only one domain and don't want the MYDOMAIN\ prefix on each user name
SSPIOmitDomain On

# AD user names are case-insensitive, so use this for normalization if your application's user names are case-sensitive
SSPIUsernameCase Lower
AuthName "Some text to prompt for domain credentials"
Require valid-user

And don't forget that you can also use Firefox for transparent SSO in a Windows domain: Simply go to about:config, search for network.automatic-ntlm-auth.trusted-uris, and enter the host name or FQDN of your internal application (like myserver or myserver.corp.domain.com). You can have more than one entry, it's a comma-separated list.

Single Sign On (SSO) - workflow

Do I retain the access token and request information from the API source each time they login?

If the token does not expire, you can hold on to it in a data store and use it with each request. Many times, though, the token will expire, and you need to request a new one each time you start a session. In this case you'd probably store the token in memory instead of a permanent storage location.

How do I relate my own application data to the API data?

I think we'd need to know a little more about your application to answer this question.

Would I create a different kind of user record that just contains the access token and application's userid?

Again, we'd probably need a little more information about your application. If you were persisting the token (in the case that it doesn't expire), then you need to make some considerations about how you want to store it. If not, you can probably just put it into a local variable or session.

Single Sign On between Wordpress and a Custom Website

Try this, called Single Sign On or SSO. You would essentially be bypassing WordPress' authentication methods and use those from a 3rd party. WordPress needs a local user of some kind for normal operations to work (the user_can() function for example.)

So the general gist would be something like this - Send any request for authentication to a 3rd party who will yay or nay and return some data about the user - if yay, check if there is a WordPress user that represents the authenticated user. If not, create one and add any meta data you might need from the third party. - these WordPress users are like placeholders and every time you successfully authenticate, you update the meta data of the WordPress user to keep things in sync.

This is how OneLogin does it and you can poke around their WordPress plugin to get a sense of how it works.

https://wordpress.org/plugins/onelogin-saml-sso/

Or You can refer this also.

http://carlofontanos.com/auto-login-to-wordpress-from-another-website/

Best way to implement Single-Sign-On with all major providers?

As original author of this answer, I want to note that I regard it as
OUTDATED. Since most providers decided to exclusively implement Oauth instead of Openid. Newer Openid services will also likely use
openid connect, which is based on oauth. There are good libraries like for example: https://github.com/hybridauth/hybridauth

After the discussion of the already existing answer i sum up:

Almost every major provider is an openid provider / endpoint including Google, Yahoo, Aol.

Some of them requrie the user to specify the username to construct the openid endpoint.
Some of them (the ones mentioned above) do have discovery urls, where the user id is automatically returned so that the user only has to click. (i would be glad if someone could explain the technical background)

However the only pain in the ass is Facebook, because they have their Facebook connect where they use an adapted version of OAuth for authentication.

Now what I did for my project is to set up an openid provider that authenticates the user with the credentials of my facebook Application - so the user gets connected to my application - and returns a user id that looks like:

http://my-facebook-openid-proxy-subdomain.mydomain.com/?id=facebook-user-id

I also configured it to fetch email adress and name and return it as AX attributes.

So my website just has to implement opend id and i am fine :)

I build it upon the classes you can find here: http://gitorious.org/lightopenid

In my index.php file i just call it like this:

<?php
require 'LightOpenIDProvider.php';
require 'FacebookProvider.php';
$op = new FacebookProvider;
$op->appid = 148906418456860; // your facebook app id
$op->secret = 'mysecret'; // your facebook app secret
$op->baseurl = 'http://fbopenid.2xfun.com'; // needs to be allowed by facebook
$op->server();
?>

and the source code of FacebookProvider.php follows:

<?php
class FacebookProvider extends LightOpenIDProvider
{
public $appid = "";
public $appsecret = "";
public $baseurl = "";

// i have really no idea what this is for. just copied it from the example.
public $select_id = true;

function __construct() {

$this->baseurl = rtrim($this->baseurl,'/'); // no trailing slash as it will be concatenated with
// request uri wich has leading slash

parent::__construct();

# If we use select_id, we must disable it for identity pages,
# so that an RP can discover it and get proper data (i.e. without select_id)
if(isset($_GET['id'])) {
// i have really no idea what happens here. works with or without! just copied it from the example.
$this->select_id = false;
}
}

function setup($identity, $realm, $assoc_handle, $attributes)
{
// here we should check the requested attributes and adjust the scope param accordingly
// for now i just hardcoded email
$attributes = base64_encode(serialize($attributes));

$url = "https://graph.facebook.com/oauth/authorize?client_id=".$this->appid."&redirect_uri=";

$redirecturl = urlencode($this->baseurl.$_SERVER['REQUEST_URI'].'&attributes='.$attributes);
$url .= $redirecturl;
$url .= "&display=popup";
$url .= "&scope=email";
header("Location: $url");
exit();

}

function checkid($realm, &$attributes)
{
// try authenticating
$code = isset($_GET["code"]) ? $_GET["code"] : false;
if(!$code) {
// user has not authenticated yet, lets return false so setup redirects him to facebook
return false;
}

// we have the code parameter set so it looks like the user authenticated
$url = "https://graph.facebook.com/oauth/access_token?client_id=148906418456860&redirect_uri=";

$redirecturl = ($this->baseurl.$_SERVER['REQUEST_URI']);
$redirecturl = strstr($redirecturl, '&code', true);
$redirecturl = urlencode($redirecturl);
$url .= $redirecturl;
$url .= "&client_secret=".$this->secret;
$url .= "&code=".$code;
$data = $this->get_data($url);

parse_str($data,$data);

$token = $data['access_token'];

$data = $this->get_data('https://graph.facebook.com/me?access_token='.urlencode($token));
$data = json_decode($data);

$id = $data->id;
$email = $data->email;
$attribute_map = array(
'namePerson/friendly' => 'name', // we should parse the facebook link to get the nickname
'contact/email' => 'email',
);

if($id > 0) {

$requested_attributes = unserialize(base64_decode($_GET["attributes"]));

// lets be nice and return everything we can
$requested_attributes = array_merge($requested_attributes['required'],$requested_attributes['optional']);
$attributes = array();
foreach($requested_attributes as $requsted_attribute) {
if(!isset($data->{$attribute_map[$requsted_attribute]})) {
continue; // unknown attribute
}
$attributes[$requsted_attribute] = $data->{$attribute_map[$requsted_attribute]};
}

// yeah authenticated!
return $this->serverLocation . '?id=' . $id ;
}
die('login failed'); // die so we dont retry bouncing back to facebook
return false;
}
function get_data($url) {
$ch = curl_init();
$timeout = 5;
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch,CURLOPT_CONNECTTIMEOUT,$timeout);
$data = curl_exec($ch);
curl_close($ch);
return $data;
}

}

Its just a first working version (quick and dirty)
Some dynamic stuff is hardcoded to my needs.
It should show how and that it can be done.
I am happy if someone picks up and improves it or re writes it or whatever :)

Well i consider this question answered

but I add a bounty just to get discussion. I would like to know what you think of my solution.

I will award the bounty to the best answer/comment beside this one.



Related Topics



Leave a reply



Submit