Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I already did a lot of research on this topic and have implemented a lot of solutions myself.

Including OpenID, Facebook Connect (using the old Rest API and the new Graph OAuth 2.0 API), Sign in with twitter (which has been upgraded to fully qualified OpenID by now as far as I know), and so on...

But what I'm still missing is the perfect all in one solution.

During my research I stumbled about some interesting projects:

  • Janrain (formerly RPX) - a commercial solution
  • Gigya - a free but externally hosted solution with javascript and rest apis
  • AnyOpenID - a free solution for clients, commercial for websites

But I don't want to rely on an external provider and I would like a free solution as well, so I am not limited in implementation.

I have also seen developers implementing one service after another dutifully following the providers instructions and setting up models and database tables for everything.

Of course this will work but it is a shitload of work and always needs development and changes in your application etc.

What I am looking for is an abstraction layer that takes all the services out there to one standard that can be integrated in my website. Once a new service appears I only want to add one model that deals with the abstraction of that specific provider so I can seamlessly integrate it into my application.

Or better, find an already existing solution that I can just dowonload.

Ideally this abstraction service would be hosted independently from my application so it can be used for several applications and be upgraded independently.

The last of the 3 solutions above looks promising from the concept. Everything is just ported to an synthetic OpenID, and the website jut has to implement OpenID.

After a while i found Django socialauth, a python based authentication system for the Django Webframework. But it looks like it operates as described above and i think this is the same login system that Stackoverflow uses (or at least some modified fork...).

I downloaded it and tried to set it up and to see whether it could be set up as a standalone solution but I had no luck, as I am not so into python either.

I would love a PHP based solution.

So after this long text my question precisely is:

  • How would you implement SSO, any better idea than porting everything and have OpenID as basis?
  • What are the pros and cons of that?
  • Do you know any already existing solutions? Preferrably open source.

I hope this question is not too subjective, thanks in advance.

Update: I concluded that building a proxy / wrapper or what you might call it for Facebook, to port it to an OpenID so it becomes an OpenID endpoint / provider would be the best option. So that exactly what i did.

Please see my answer below.

I added the bounty to get feedback/discussion on it. Maby my approach is not so good as i currently think it is!

like image 774
The Surrican Avatar asked Oct 31 '10 02:10

The Surrican


People also ask

What is the best single sign-on solution?

The Best Single Sign-On Solutions Include: Duo Single Sign-On | Ping Identity | Thales SafeNet Trusted Access | JumpCloud SSO | CyberArk Workforce Identity | Microsoft Azure Active Directory | Okta Single Sign-On | OneLogin Secure Single Sign-On | RSA SecureID Access | SecureAuth Identity Platform.

How do you implement SSO?

In the management dashboard, click Apps / APIs. Click the application that you want to enable Single Sign On. In the Settings tab, scroll down until you see the Use Auth0 instead of the IdP to do Single Sign On switch. Flip the switch! and save the changes.

What is the most common SSO?

SSO can be achieved in various ways, but the most common approach is federation; the user logs into an identity provider (IDP) service. The IDP hands off a token, assertion, or ticket to an application in order to gain access without asking the user to re-authenticate.


2 Answers

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.

like image 118
The Surrican Avatar answered Sep 23 '22 12:09

The Surrican


OpenID is going to be your best bet for this application. It is supported by many, providers:

  • Google
  • Yahoo
  • MyOpenID
  • AOL

The Only problem is that twitter has not implemented OpenID yet. This is probably due to the fact that they are a proprietery based company, so they wanted their 'own' solution.

To solve that solution, you might write a wrapper class to provide compatibility with OpenID, but the chance is that even if your users don't have a twitter account, they might have a Facebook, Google, or Yahoo account.

Facebook Supports oauth, so you will have to port oauth to OpenID

Some PHP libraries for OpenID can be found here.

Now, some questions have been raised about facebook being an oauth provider.

Their oauth URL is "https://graph.facebook.com/oauth/authorize"

If you still do not belive me, then you can look at this javascript file, where I got that URL. If you don't believe that javascript file, then notice that it is hosted by stackexchange, the provider of this site. Now you must beleive that.

like image 30
xaav Avatar answered Sep 22 '22 12:09

xaav