administration mode
Pssst...Ferdy is the creator of JungleDragon, an awesome wildlife community. Visit JungleDragon

 

How to integrate Twitter and Facebook using oAuth »

FERDY CHRISTANT - NOV 13, 2010 (04:01:44 PM)

In my previous post, I announced that JungleDragon now has signin support for Twitter and Facebook.In this post I will share some implementation details. Unfortunately, I cannot package my solution as a stand-alone demo for you to just take and reuse. Much code is too specific for my situation and would also be different for your situation. Nevertheless, I will do my best to share some design considerations, sample code and tips.

The what and the why...

We want users to be able to sign in to our web application using their existing Twitter or Facebook account. This boils down to two use cases: joining your site and signing in to your site (once joined). 

There are a number of reasons why you might like to implement this:

  • Most users hate creating yet another account for your web app
  • You outsource a lot of user management and security features to Twitter or Facebook
  • It lowers the barrier for users to join your site
  • If applicable, it allows for even more advanced social networking integration

External authentication or hybrid?

Before you go to implement external authentication, there is a key design decision to make: will external authentication be the only way to sign up and sign in, or will you implement a hybrid model? In a hybrid model, users can choose to use external authentication or normal authentication, meaning they create and manage their account using your user management system. 

If you choose to implement external authentication only, you save yourself the trouble of developing your own user management system (although you still need to develop a lightweight one anyway). And, the user can reuse their existing accounts. The best of both worlds?

No. Not for users who do not have an account at Twitter or Facebook. Also not for users who actually prefer to keep their accounts seperated. It will largely depend on your audience whether this is acceptable. If you want the widest possible reach and best usability, you will need to implement the hybrid model.

Implementing the hybrid model is like the worst of both worlds from a developers perspective. You will need to implement external authentication AND your own user management system, and deal with the complexities between them. It is doable, but not a task to be underestimated.

OpenID or oAuth?

There are two common ways to do external authentication in an open way: OpenID and oAuth. If you have visited sites that implement OpenID or oAuth you could argue that for a user, they do the same thing: allowing them to log on to a third party website system using their preferred signin provider. 

Conceptually, there is a difference though. OpenID is an open, decentralized identity provider in the purest sense. It concerns identity management only. oAuth, however, really is a permission system not neccessarily about identity management only. oAuth is about sharing data between websites without sharing passwords. For example, using oAuth, your website could post messages on a user's Twitter account if they allow you. This is not possible with OpenID, as that manages identity only. 

In my implementation I have chosen to use oAuth because I need more than just the signin experience: I also need data from the user profile. And perhaps, in the future I want to more deeply integrate into Twitter and Facebook.

Some more food for thought...

So far we have decided upon implementing a hybrid model (external + internal authentication) using oAuth. Before we discuss the implementation approach, there are a few more decisions to make in what you will support:

  • Should it be possible for users to use multiple signin providers for a single account at your web app?
  • Should users with a normal account be able to link external signing providers to their account?

If you want to stay sane, say NO to both questions. If you want the best usability, say YES.

How does oAuth work?

oAuth is an open standard so as developers we expect to produce reusable code that we can use for multiple providers, Twitter, Facebook, right? The pattern of using oAuth for providers is the same across applications, that much is true. However, there are subtle differences in how you can interact with these providers. First, let us discuss the rough pattern. Here is a nice detailed explanation of this flow, but I'll make an attempt to put this in simpler terms:

  • One-time: Your register your application at the provider and receive a private and a public key
  • You place a button "Sign in to Twitter" on your website and the user clicks on it
  • Your app cannot just go and ask for data about the user. It needs to ask if it may ask the question in the first place. Therefore your app will make a request to the provider passing in the private and public keys.
  • The provider checks the keys and validates if the application exists. If so, it will return a request token. "Ok app, we know you...go ahead and ask now"
  • Using the request token, our app will then direct the user to the authentication gateway of the provider, passing in the request token
  • At the provider's site, the user is asked to authenticate, if not authenticated already.
  • At the provider's site, the user has to allow the application access to their account. This is a one-time step.
  • Upon success of the two previous steps, the provider will redirect the user back to the app, passing in an access token.
  • Back at our application, we still have no data. However, we have an access token. Using this access token, which is unique per user, we can ask for data using the API of the provider. We pass in the access token and receive the data we requested. This access token is permanent. We can use it to interact with the API on behalf of the user even when he is disconnected.
  • We will typically call the API to get the user's unique username from the provider. Once we have this returned, we can work out our own logic inside our application, for examply by creating a user in our database.

It may seem like a lot, but technically it is just a matter of calling the right URL with the right keys and reading the response.

Can we get to the code now?

Yes we can.The first thing to do is to is to register your application at each provider, which you can do at their website. You need to do this for each application instance. So, if you have a dev, test and production instance of your application, you need 3 registrations. At registration, you are typically asked to name and describe your application. You will also need to set the unique URL of your application. Some providers allow you to set a callback URL (to which the provider will redirect after the user allows access to their account), however you can also dynamically set the callback URL at runtime.

After registration, there will be 3 pieces of configuration data that you will need to store in your application settings, be it a config file or a constant. These are:

  • Consumer key, sometimes called API key.
  • Consumer secret key
  • Callback URL, if you choose to dynamically set it at runtime.

I have PHP as a back-end and CodeIgniter as my framework and have set these config params in a config file like so:

$config['pd_oauth_twitter_consumerkey'] = "your key here";
$config['pd_oauth_twitter_consumersecretkey'] = "your secret key here";
$config['pd_oauth_twitter_callbackurl'] = "your callback url here";

$config['pd_oauth_facebook_consumerkey'] = "your key here";
$config['pd_oauth_facebook_consumersecretkey'] = "your secret key here";
$config['pd_oauth_facebook_callbackurl'] = "your callback url here";

Be sure to never share your keys with anyone.

The signin controller

In our code, we should support the following scenarios:

  • User logs in using native username/password using our own user management system
  • User logs in using authentication provider (Twitter or Facebook)
  • User is logged into their native account yet wants to add an external account
  • User joins using an external account (Twitter or Facebook)

In my implementation, all of these events are routed to the same URL and thereby the same controller. It is a single place to handle all this logic. The URL setup is as below:

mydomain.com/signin (user signs in using native account)
mydomain.com/signin/[provider] (user signs in using [provider])
mydomain.com/signin/[provider]/callback (user is authenticated externally and redirected back)

Before we go into the complexity of this controller, let us first setup our database.

The database model

The following diagram shows the relevant tables concerning user management and authentication:

It is very important that in your database model and code, you seperate the concepts of user accounts, user provider accounts and sessions. Let us briefly discuss each table:

User

This is our internal user table (note that not all fields are visible on the screenshot). We still need this because we want to support non-oAuth users too. What you put in this table is all up to you, but it is important to have a unique internal user id. You may also want a status field to indicate if a user has activated their account yet.

In my case I have also added an "auth" field. This field indicates the signin method used by the user to sign up. 

UserProvider

On user can have multiple signing accounts at different providers, therefore we need to track those in this seperate table. An explanation of the fields:

  • id: primary key, auto-incremented
  • user_id: foreign key to user.id, indicates for which user this oauth account is
  • provider: the name of the provider (Twitter, Facebook, ....)
  • auth_id: the unique id of the user at the provider
  • auth_username: the name of the user at the provider
  • auth_token: the permanent access token of the user at the provider
  • auth_token_verifier: only needed if we use dynamic callback URLs

Session

You may not have your sessions stored in a database but that's OK. The point is to have an internal session store and not confuse it with the session at the external provider. At the very least your session will have a pointer to user.id to indicate for which user the session is.

Back to our controller

We're all setup now and ready to implement our controller. It is a single place to handle all authentication and join logic, yet it is somewhat complex. There I will break it down per incoming URL:

mydomain.com/signin (user signs in using native account)

In our code, we detected that this is a normal signin, since no provider name is passed into the URL. This means the user is signing using their native account. The implementation of this is of course very specific to your user management code, but in my case it works like this:

  • The incoming request is a GET request: The user clicked the signin button. Present our internal signin form.
  • The incoming request is a POST request: The user submitted the signin form. Do your custom validation and logic for signing in the user into your application.

Again, this part is entirely up to you and depends on your internal user management logic.

mydomain.com/signin/[provider] (user signs in using [provider])

In our code, we detected that this is not a normal signin, since a provider name was passed in. However, since there is no "/callback" part of the URL, we know the user is at the first step of this process, meaning they clicked the "Twitter" or "Facebook" button. This could come from both our signin and join form. 

The thing to do in this case it to make the redirect to the provider. We will make a class or function called "get_oauth_redirecturl" with the following signature:

function get_oauth_redirecturl($provider) {}

From our controller we call this method and pass in the provider name that we received from the URL request. We will use the return value to do the redirect:

$redirect_url = $this->auth->get_oauth_redirecturl($provider);
if ($redirect_url) { header('Location: ' . $redirect_url); }

The reason we pass in the provider into the function is because the way we redirect to the provider is very provider specific. Facebook is the easiest to handle, this code generates a redirect to make for oAuth:

return "https://graph.facebook.com/oauth/authorize?" . http_build_query(array(
'client_id' => $this->CI->config->item("pd_oauth_facebook_consumerkey"),
'redirect_uri' => $this->CI->config->item("pd_oauth_facebook_callbackurl")
));

This just build a URL with querystring using the config values we set earlier. Now on to Twitter. For Twitter we first need to get request tokens. I am using the following library to get the redirect URL:

https://github.com/abraham/twitteroauth

My code to get the Twitter redirect URL:

require_once(APPPATH . "libraries/twitteroauth.php");

// create a new oauth object using the consumer keys
$connection = new TwitterOAuth(
$this->CI->config->item("pd_oauth_twitter_consumerkey"),
$this->CI->config->item("pd_oauth_twitter_consumersecretkey")
);

// get temp credentials from Twitter
$temporary_credentials = $connection->getRequestToken($this->CI->config->item("pd_oauth_twitter_callbackurl"));

// put temp credentials in session, we need this in the callback
$this->CI->session->set_userdata('oauth_token', $temporary_credentials['oauth_token']);
$this->CI->session->set_userdata('oauth_token_secret', $temporary_credentials['oauth_token_secret']);

return $connection->getAuthorizeURL($temporary_credentials);

There is some code specific to CodeIgniter and my session handler, but I hope you understand the main flow here. This completes our redirect logic. The user will be taken to the provider, sign in there (if not already done), authorize our application (if not already done) and then be redirected to our callback URL, which is the final step in our flow:

mydomain.com/signin/[provider]/callback (user is authenticated externally and redirected back) 

This is the URL that is called when the user returns at our site. Actually, the URL will have or two additional parameters passed in via the provider:

mydomain.com/signin/[provider]/callback?oauth_token=[access token]&oauth_verifier=[verifier]

Note: the naming of these parameters may vary per provider.

Since the /callback segment is now part of the URL, our controller knows that this concerns a user returning from an authentication provider. We are now in the stage where we have a permanent access token for a user, yet no user identifier (name or id) yet. We use the access token to make a request to the API of the provider. How this works and what will be returned differs per provider. We will create a method called get_oauth_user and pass in the provider name from the URL:

function get_oauth_user($provider) {}

This method will make the API request and return with an array of user details, if successfull. First, the Twitter way. I am assuming you are including the Twitter oAuth lib discussed before:

$connection = new TwitterOAuth(
$this->CI->config->item("pd_oauth_twitter_consumerkey"),
$this->CI->config->item("pd_oauth_twitter_consumersecretkey"),
$this->CI->session->userdata('oauth_token'),
$this->CI->session->userdata('oauth_token_secret')
);

// get long lasting credentials
$token_credentials = $connection->getAccessToken($tokenverifier);

// build up a new connection using long lasting credentials
$connection = new TwitterOAuth(
$this->CI->config->item("pd_oauth_twitter_consumerkey"),
$this->CI->config->item("pd_oauth_twitter_consumersecretkey"),
$token_credentials['oauth_token'],
$token_credentials['oauth_token_secret']
);

$content = $connection->get('account/verify_credentials');
$result = $connection->get('users/show', array('screen_name' => $content->screen_name));

return array(
"id"=>$result->id,
"name"=>$result->name,
"location"=>$result->location,
"description"=>$result->description,
"avat_url"=>$result->profile_image_url,
"token" => $token_credentials['oauth_token'],
"tokenverifier" => $tokenverifier
);

Notice how I am not only getting the Twitter username, but also additional details that I want to reuse in my internal user store. These may vary on your own needs. Hereby the Facebook way of getting user details:

$error_reason = $_GET['error_reason'];

// something went wrong
if ($error_reason) return false;

// get access token
$response_url = "https://graph.facebook.com/oauth/access_token?" . http_build_query(array(
'client_id' => $this->CI->config->item("pd_oauth_facebook_consumerkey"),
'redirect_uri' => $this->CI->config->item("pd_oauth_facebook_callbackurl"),
'client_secret' => $this->CI->config->item("pd_oauth_facebook_consumersecretkey"),
'code' => $code
));

// get the response content
$response = file_get_contents($response_url);

// parse the response
$result = array();
parse_str($response,$result);

if (isset($result['access_token'])) {
// we have an access token. get user details
$userdetails = file_get_contents("https://graph.facebook.com/me?" . http_build_query(array("access_token"=>$result['access_token'])));

$this->CI->load->helper("json_helper");
$user = (array)my_json_decode($userdetails);
$location = (array)$user['hometown'];
$location = $location['name'];

return array(
"id"=>$user['id'],
"name"=>$user['name'],
"firstname"=>$user['first_name'],
"lastname"=>$user['last_name'],
"location"=>$location,
"gender"=>$user['gender'],
"avat_url"=>'',
"description"=>'',
"avat_url"=>'',
"token" => $result['access_token'],
"tokenverifier" => $code
);

Good, with that method implemented, we now have a way to get the details of the oAuth user that was redirected back to our site. Now, what do we do with it? There are a couple of scenarios that we need to implement, allow me to walk by them step by step. The first thing to do is to use the id of the oAuth account (user['id']) to do a lookup in our internal userprovider table to see if this concerns a known user.

The user is not known yet, and was not logged in our internal session.

This essentially is a "join" action. Here you place your logic when a user joins. Typically it will involve creating the user record. In my case it asks the user for one additional step: to set their email address, which they need to activate. The reason: oAuth providers generally do not hand out a user's email address.

If you require usernames to be unique in your internal user store, you will first need to check the oAuth username against your store. If it concerns a duplicate name, you need to tell the user that they cannot join or offer the option for them to manually set another name.

The user is not known yet, yet was logged in our internal session

If the user has an internal session, it means the user was trying to link an external account to their existing account. In this case, we need to create a new entry in the userprovider table and link it to their user account. Upon success, we redirect the user to his profile showing his signin accounts.

The user is known

We have a known oAuth user. This means they are not joining but signin in. In this step, you will typically do the following:

  • If applicable, check their user status. If your system requires email activation and the user has not carried out this step, you should tell users to first activate their account and block the signin
  • Optionally, you can update some fields on the user record, such as their last login date. Another interesting option is to resycn their Twitter or Facebook data on their internal user profile
  • If everything is successful, set the user's session. They are now logged in. Note that your internal session has nothing to do with the oAuth session user's may have.

Final steps

If you're still reading, congratulations. It was a lot to handle but we have covered most of the work needed to implement a flexible, hybrid authentication system for users. There are a few more small things to consider given that we using a mixed model of native and external accounts:

  • Users that created their account using Twitter or Facebook should be blocked from some of your internal user management functionalities, such as "resetpassword" and a normal signin form. Since they do not manage their credentials internally, you should not confuse them with these options.
  • If you have a way for users to edit their profile details, you may want to consider blocking some fields that you are syncing from the oAuth provider. You could also not block them and let the user overrule the default values they get from their oAuth account.
  • oAuth integration has no impact on your signout code, since your session is seperate from the oAuth session
  • Your code for deleting or banning users may or may not have to change, depending on what you want to do. If you physically delete users, also delete their userprovider records, or configure a cascading delete in your database.

Wrapping up

Again, I am sorry that I cannot give a ready-to-go code package for you to plugin. The implementation that you need depends on your platform, framework and internal user management system. Still, I hope this article explains you somewhat what it takes to implement a mixed authentication model using oAuth. My conclusion is that it is not easy, yet doable, and definitely worth it for your users. Including all my research time and trial and error, I'd say it took me about 3 days to fully implement, given an already existing internal user management system. I hope this article can reduce that time for others.

Please rate and leave your feedback and questions in the comments!

If you want to see and try how this works for real, check out the JungleDragon staging environment!

Share |

Comments: 25
Reviews: 9
Average rating: rating
Highest rating: 5
Lowest rating: 4

COMMENT: NIKHIL NIGADE rating

NOV 14, 2010 - 07:38:42 PM

comment » Wow, this was great and really easy to understand. You made it look simple to implement. I hope it really easy that simple ( I can understand a +1 on the difficulty part for me 20 ) «

COMMENT: FERDY

NOV 15, 2010 - 08:25:39

comment » @Nikhil: Thanks, that looks like a mission accomplished. It was my goal to make this easier for others. With the guidance above it really is not that difficult. For me it was though, as I had to figure out all of this stuff on my own 18 «

COMMENT: XAVIER P email

NOV 19, 2010 - 11:13:35

comment » Hi,

first thanks for this article, it was very very usefull ! 12

but can you help me a bit?!

I'm having a probleme at the end of the process..

In fact i'v got the auth from Facebook, and Facebook forward user's to my callback url but when the user if forwarded to my website back i got a HTML303 (too many redirect error 04 )

I'm not finding any solutions 03 so if you have any idea 28

Thanks by advence ! «

COMMENT: XAVIER P emailrating

NOV 19, 2010 - 11:19:08

comment » Sorry , i made a mistake, it's not a 303 but a 310 error :

net::ERR_TOO_MANY_REDIRECTS)

05 «

COMMENT: FERDY

NOV 20, 2010 - 12:43:55

comment » @Xavier. Well, it looks like you are in a circular redirect loop. Uncomment all your redirect statements and start doing some logging. I cannot be more helpful if you don't share your code. «

COMMENT: ART T. rating

JAN 19, 2011 - 03:05:01 PM

comment » Cool, I've implemented just yesterday exactly this without ever reading this article. So I came to exactly this idea of how to integrate "external" users into our future project and how to resolve the username collisions with "internal" users. Cool ;) «

COMMENT: FERDY

JAN 19, 2011 - 19:25:23

comment » @Art T: It seems great minds think alike :) «

COMMENT: TOKS email

JAN 26, 2011 - 08:21:45 AM

comment » Hello,

Thanks for this great tutorials, i have being having issues presently on how to integrate facebook and twitter together.

But with this tutorial i believe it is going to be of a great help, i will not mind if you can help me with the source code, every thing in a place so that i can easily understand the whole thing.

Thanks «

COMMENT: SHIFT

FEB 3, 2011 - 05:31:19 AM

comment » Thanks so much for making this available.

Would really appreciate it if you could run through how this can be simplified for sites that don't need their own native user accounts and just want to rely on twitter and facebook for registration/login. I'm guessing this would make things a lot easier. «

COMMENT: FERDY

FEB 3, 2011 - 11:02:12 AM

comment » @Shift. Yes, that would be a lot easier. You would not need to build a login form, registration form, reset password form, edit email form, etc. You would still need an internal table of users, but not much more than that. «

COMMENT: SHIFT rating

FEB 3, 2011 - 04:10:26 PM

comment » Can't thank you enough for putting this tutorial online Ferdy.

It's taken me a few reads to really understand what's going on and work out how to adapt it to my own needs, but I think I've got it now. «

COMMENT: JEFERSON SILVEIRA emailhomepage

APR 19, 2011 - 03:50:55

comment » Ferdy, great post. Congratulations!

I believe that if possible the publication of some files would help in understanding and implementing on other sites.

Could publication of the controller "singin ', the screen with links. Also the model with details of the three tables?

I understand the best way.

Thanks!

**Sorry for english, i'm from Brazil :) «

COMMENT: FERDY

APR 20, 2011 - 07:41:24 AM

comment » Jeferson, I'm sorry but that is not possible. I already explained why in the beginning of the article. I understand it would be convenient to have a ready-to-example, but my example is way too specific for my case. «

COMMENT: MESA email

JUL 22, 2011 - 07:50:20 PM

comment » Hi there,

What happens when a user so happens to sign in with both twitter and facebook at the same time. I see that session_start() is used for both php sdks. so In my mind I see confusion in the $_SESSIONS possible. Although this may be unlikely to happen it sure can I think. How can this be mitigated. On the end of the web app, once the user is authenticated we can say you are already aunthenticated we are not authenticated you again unless you sign out and sign back in, or we can kill the current authenticated session and start a new session. The session I am talking about here is the web app session using a back end db to store the session.

But now with the facebook/twitter sessions we can really stop a user from signing in to facebook/twtitter once we give the functionality. And I see there may be strange errors occuring with php session collisions and conflicts. Thoughts? «

COMMENT: FERDY

JUL 23, 2011 - 10:39:57

comment » Mesa, I honestly don't see the problem. The web app session is completely seperate from both Twitter and Facebook. Once the web app session is established it is no longer relevant whether you are signed in on Twitter, Facebook, or both. You could also sign out of both and it still would not matter.

In a concurrent sign-on scenario the first session cookie to be processed by the server will win. «

COMMENT: ORIGINALITY emailrating

AUG 23, 2011 - 08:16:16

comment » In the part "The user is not known yet, and was not logged in our internal session", should you also ask the user the set the local password, so that in the future he can sign in using the local username and local password (especially for those who forgot whether he joins through FB or our own user mgmt system, and wanna have shot with his usual username and pass)

Besides, for some websites with their own established user mgmt system, if we skip this step (setting the local password), it might lead to security risks of "empty passwords." Am I right? Look foward to your advice. «

COMMENT: ARI email

APR 3, 2012 - 01:45:08 AM

comment » Is it possible to get a copy of the CI code you created here (e.g. the routes and controller)?

Best,

--Ari «

COMMENT: LUCACI ALEXANDRU-ADRIAN homepagerating

SEP 1, 2012 - 05:51:51 PM

comment » Fantastic article.

I found here all the info I needed. Thank you!

I will try to implement everything as soon as possible and hope it all works as planned. «

COMMENT: MARINA rating

SEP 29, 2012 - 11:36:00 AM

comment » Well explained, thx «

COMMENT: RICHARD

DEC 7, 2012 - 09:56:42

comment » This is a great article and it really help get my authentication library of the ground. I do have one question.

What happens when a user sign in first with Facebook, then on another visit signs in with Twitter. Would you link the userprovider with the user with the same email address (when you ask for the email) or would you consider them two separate users.

I ask because twitter does not provide the email address and even if they did there is noway of knowing they used the same email for Facebook and Twitter.

Best «

COMMENT: FERDY

DEC 10, 2012 - 19:22:51

comment » @Richard: Thanks for the compliments.

The choice is up to you, but I would recommend seperating authentication methods from users. In my database, I have a table of users, and each user can have one or more authentication methods. This way, they can log in using Facebook, Twitter and now also Google, and the system is smart enough to know that this concerns the same user. «

COMMENT: KAZEEM rating

JUN 6, 2013 - 03:06:56 PM

comment » This is a great and very explicit article.Good job

Thanks «

COMMENT: ANIL PUGALIA homepagerating

JUL 15, 2013 - 05:42:28 PM

comment » I found no other better article than this for me to able to implement hybrid authentication system on my website. I was able to implement the complete provider logic within a day, just because of this article. Thanks a ton for such a lucid working explanation. «

COMMENT: PROX

OCT 7, 2013 - 12:30:15 PM

comment » Thanks. I am building something. But i am still a bit confused. You say: "the system is smart enough to know that this concerns the same user." But how is that? If i login by facebook and later by twitter; how does the system knows it is me again? «

COMMENT: FERDY

OCT 14, 2013 - 07:54:38 PM

comment » @Prox. The thing both logins would have in common is the email address, but you need to code that logic. «

RATE THIS CONTENT (OPTIONAL)
Was this document useful to you?
 
rating Awesome
rating Good
rating Average
rating Poor
rating Useless
CREATE A NEW COMMENT
required field
required field HTML is not allowed. Hyperlinks will automatically be converted.