Social Enterprise

Using Twitter to power registration systems

Why spend time writing, testing and maintaining your site's registration system when you can have third parties do all the heavy lifting for you?

Web developers that have left the confines of a CMS/Wordpress solution, and ventured into the world of custom solutions know about the pain of creating and running a user log-in and registration system. Much of the time is spent reinventing wheels that were reinvented when they were created.

There are also security implications to consider with having usernames and passwords stored, especially when using credit-card details. Suddenly, you have a honeypot on your hands, and need to care about salting, the various different hashing techniques, and password retrieval and resetting.

It's all an awful amount of work to do to let people comment on content or use extra features of the site.

So why not offload the ins and outs of registration and password management to one of the big networks? When starting out, using another site's online identity is a good way to sidestep the issue, and get to coding what really matters.

A sample of the choices available include Facebook, Twitter, Windows Live, Google, and OpenID.

Today, we will start with Twitter, and we'll begin by bolting Twitter onto an existing registration system.

Using Twitter on an existing site

Adding Twitter onto an existing registration system gives a good foundation to learning how the social network handles its authentication with OAuth.

First, you'll need a Twitter account. and you'll need to fill out the new app form to create a Twitter application. Feel free to put in as much dummy data as you like; everything can be changed later on, when it is ready to be seen by the outside world. If you do not have an internet host available, you can use http://127.0.0.1/ in the website field.

For a web application, make sure that you add an explicit callback URL, even a temporary dummy one, like http://127.0.0.1/path/to/your/file.htm, otherwise, when we get to the point of actually authenticating with Twitter, the user will be asked to enter a PIN in your site. This is not the behaviour that we are after, and is meant to be used by native applications.

After submitting the form, the consumer key and consumer secret will be presented, and the application will be given read-only permissions. This access level is fine for our purposes.

Now we are ready to start interacting with the Twitter OAuth APIs. It's possible to do everything with bespoke PHP and curl calls, but I've used the oauth-php library in the past to bolt social identities onto an existing registration system before, and it takes much of the pain and numerous calls of OAuth out of sight.

A database will be needed to store server details and user tokens, and oauth-php supports Oracle, MySQL, and PostgreSQL -- choose your usual database to suit your needs, and set up the default schema using the files in oauth-php/store/[Your DB]/ directory.

Since we are only interested in the consumer side of OAuth, feel free to dispose of the oauth_server_* tables, if you dislike extraneous tables.

Let's add the Twitter server into our database's consumer registry by using the following code:


<?php

include_once "libs/OAuthStore.php";

include_once "libs/OAuthRequester.php";

define("TWITTER_CONSUMER_KEY", "FILL_THIS");

define("TWITTER_CONSUMER_SECRET", "FILL_THIS");

define("TWITTER_OAUTH_HOST","https://api.twitter.com");

define("TWITTER_REQUEST_TOKEN_URL", TWITTER_OAUTH_HOST . "/oauth/request_token");

define("TWITTER_AUTHORIZE_URL", TWITTER_OAUTH_HOST . "/oauth/authorize");

define("TWITTER_ACCESS_TOKEN_URL", TWITTER_OAUTH_HOST . "/oauth/access_token");

define('OAUTH_TMP_DIR', function_exists('sys_get_temp_dir') ? sys_get_temp_dir() : realpath($_ENV["TMP"]));

$options = array('server' => 'YOUR_SERVER', 'username' => 'YOUR_USERNAME', 'password' => 'YOUR_PASSWORD', 'database' => 'YOUR_DATABASE');

$store = OAuthStore::instance('YOUR_DB_ENGINE', $options);

#### ADD SERVER

// Get the id of the current user (must be an int)

$user_id = 1;

// The server description

$server = array(

'consumer_key' => TWITTER_CONSUMER_KEY,

'consumer_secret' => TWITTER_CONSUMER_SECRET,

'server_uri' => TWITTER_OAUTH_HOST,

'signature_methods' => array('HMAC-SHA1', 'PLAINTEXT'),

'request_token_uri' => TWITTER_REQUEST_TOKEN_URL,

'authorize_uri' => TWITTER_AUTHORIZE_URL,

'access_token_uri' => TWITTER_ACCESS_TOKEN_URL

);

// Save the server in the the OAuthStore

$consumer_key = $store->updateServer($server, $user_id);

echo "Consumer key is $consumer_key";

exit;

?>

Once this has executed successfully, comment out or remove everything beneath the "#### ADD SERVER" line, as it is only needed when adding a new server. In practice, it would be better to separate this into a separate file, and use a form to make server addition easier --- but we only have one server to add, so I'll take the lazy option.

The following code presents a button that shoots the user over to Twitter to authenticate your application, and, when returned, presents the output returned from a verify_credentials call from Twitter. We call verify_credentials, because up until that point in the process the only information that we have had to work with from Twitter about the user is the $oauth_token variable. Verify_credentials returns a set of profile information about the user that we could store in our pre-existing registration system.


if(!empty($_GET)) {

// Fetch the id of the current user -- use your pre-existing login system for this

$user_id = 1;

if (empty($_GET['oauth_token'])) {

// Obtain a request token from the server

$token = OAuthRequester::requestRequestToken(TWITTER_CONSUMER_KEY, $user_id);

// Callback to our (consumer) site, will be called when the user finished the authorization at the server

$callback_uri = 'http://localhost/registr?consumer_key='.rawurlencode(TWITTER_CONSUMER_KEY).'&usr_id='.intval($user_id);

// Now redirect to the autorization uri and get us authorized

if (!empty($token['authorize_uri']))

{

// Redirect to the server, add a callback to our server

if (strpos($token['authorize_uri'], '?'))

{

$uri = $token['authorize_uri'] . '&';

}

else

{

$uri = $token['authorize_uri'] . '?';

}

$uri .= 'oauth_token='.rawurlencode($token['token']).'&oauth_callback='.rawurlencode($callback_uri);

}

else

{

// No authorization uri, assume we are authorized, exchange request token for access token

$uri = $callback_uri . '&oauth_token='.rawurlencode($token['token']);

}

header('Location: '.$uri);

exit();

}

// Request parameters are oauth_token, consumer_key and usr_id.

$oauth_token = $_GET['oauth_token'];

try

{

OAuthRequester::requestAccessToken(TWITTER_CONSUMER_KEY, $oauth_token, $user_id);

}

catch (OAuthException $e)

{

// Something wrong with the oauth_token.

// Could be:

// 1. Was already ok

// 2. We were not authorized

}

$verify_url = "https://api.twitter.com/1/account/verify_credentials.json";

$req = new OAuthRequester($verify_url, 'GET');

$result = $req->doRequest($user_id);

echo "<pre>".print_r($result,true)."\n\n\n".print_r(json_decode($result["body"]), true)."</pre>";

}

else {

echo "<form><button name='startme' value='1'>Authenicate with Twitter</button></form>";

}

Run this code a couple of times, and you'll notice that we are shot off to authenticate Twitter every time, and that there is also no ability to detect whether a user has already approved our application; that's because this code needs to piggyback on a pre-existing registration system to control the $user_id variable to work correctly.

Let me show you the problem with this: after running the above code, and having gained a token in our oauth_consumer_token table, replace the previous code sample with this, and open the page again in a new browser:


if(!empty($_GET)) {

// Fetch the id of the current user

$user_id = 1;

$verify_url = "https://api.twitter.com/1/account/verify_credentials.json";

$req = new OAuthRequester($verify_url, 'GET');

$result = $req->doRequest($user_id);

echo "<pre>".print_r($result,true)."\n\n\n".print_r(json_decode($result["body"]), true)."</pre>";

}

else {

echo "<form><button name='startme' value='1'>Authenicate with Twitter</button></form>";

}

Because we have an OAuth token for the user in our database, we have to make sure that we show the correct token to the correct user, otherwise we have ourselves a big privacy hole to deal with. This isn't the fault of oauth-php, but it is something to keep in mind and check for.

To get the details crudely into an existing users' table after a verify_credentials call:


$verify_url = "https://api.twitter.com/1/account/verify_credentials.json";

$req = new OAuthRequester($verify_url, 'GET');

$result = $req->doRequest($user_id);

$conn = pg_connect("host=YOUR_SERVER dbname=YOUR_DATABASE user=YOUR_USERNAME password=YOUR_PASSWORD");

pg_exec("UPDATE users SET twitter_id=$twitter_id, twitter_avatar='$twitter_avatar', name='$name' WHERE id=$user_id");

/**** SAMPLE USERS TABLE ******

CREATE TABLE users (

id integer,

email varchar(300) UNIQUE,

passhash char(40),

salt char(16),

name varchar(300),

twitter_id integer,

twitter_avatar varchar(300),

-- extra networks in here later

PRIMARY KEY(id)

);

***********/

And that's all we need.

One of the things that I like about the library is that you are not forced into using its default database schema if you do not want it. After getting comfortable with the way the library works, you create your own data store abstractions and plug it directly into your existing registration tables, and remove the base code by that above.

What we have just seen is useful in the linking of a Twitter account with an existing user log-in; now, let's get to the new user scenario.

Using @Anywhere

If you don't want to, you do not have to wade through the swamp of OAuth; there is a solution written in JavaScript: @Anywhere.

The benefit of @Anywhere is that it completely removes the heavy lifting of authentication, and even button design. For instance, if a user forgets their password, it's Twitter's problem; similarly, all of the other chores of registration are outside your responsibilities.

That doesn't mean you'd want to do away with a users' table altogether, even though you theoretically could. More than likely, you'll have extra data that you want store with users.

To use @Anywhere, you simply need to call twttr.anywhere function, and pass in a callback function that accepts an instance of the Twitter API as a parameter.

Inside the callback, we can create a log-in button by selecting a div and calling the connectButton method: T("#signin-btn").connectButton();

We can check whether a user has approved our application in the past by using T.isConnected(), and then fetching the user information with the T.currentUser object. From there, we are able to store that data in a database, and use it to power our application.

Below is a quick page that shows some user information once approved:

<!DOCTYPE HTML>

<html>

<head>

<meta http-equiv="Content-type" content="text/html; charset=utf-8">

<title>registr</title>

<script src="http://platform.twitter.com/anywhere.js?id=[put consumer key here]&v=1" type="text/javascript"></script>

</head>

<body>

<h1>regist<em>r</em></h1>

<span id="bio">We want your details</span>

<div id="reg_avatar"><span id="signin-btn">Loading ....</span></div>

<script type="text/javascript">

twttr.anywhere(function (T) {


if (T.isConnected()) {

currentUser = T.currentUser;

screenName = currentUser.data('screen_name');

profileImage = currentUser.data('profile_image_url');

profileImageTag = "<img src='" + profileImage + "'/>";

document.getElementById("reg_avatar").innerHTML="<strong>Logged in as:</strong><br> " + profileImageTag + " " + screenName+"<br/>Your twitter id is:"+currentUser.data('id');

console.log(currentUser);

} else {

document.getElementById("signin-btn").innerHTML="";

T("#signin-btn").connectButton();

};

T.bind("authComplete", function (e, user) {

// triggered when auth completed successfully

//console.log(e);

//console.log(user);

document.getElementById("reg_avatar").innerHTML="<strong>Logged in as:</strong><br> <img src='" + user.profileImageUrl + "'/> " + user.screenName+"<br/>Your twitter id is:"+user.id;

//This is where I would fire the asynchronous call to update the user table

//using your AJAX framework of choice

fireAjaxUpdate(user.id, user.screenName, user.profileImageUrl);

});

T.bind("signOut", function (e) {

// triggered when user logs out

//console.log(e);

});

});

</script>

</body>

</html>

If you refresh this page a few times, you'll notice that there is a time lapse between loading the page and receiving the user information from Twitter.

Thankfully, @Anywhere stores a cookie called "twitter_anywhere_identity" that you can use. The cookie has the format user_id:signature, and the signature can be checked against a sha1 concatenating Twitter user ID and your consumer secret.


<php

if(isset($_COOKIE['twitter_anywhere_identity'])){

$consumer_secret = "[your consumer secret]";

list($twitter_id, $sig) = explode(":", $_COOKIE['twitter_anywhere_identity']);

if(sha1($twitter_id.$consumer_secret) == $sig) {

echo "Correct Cookie detected";

//Can modify the page contents for logged in users at this point

}

}

?>

From this point, it's really all about how your application executes after successful log-in, or behaves for a returning user. Best of luck, and make sure to check out the rest of the Twitter documentation.

In future articles, we will look at dealing with the other social networks.

About

Some would say that it is a long way from software engineering to journalism, others would correctly argue that it is a mere 10 metres according to the floor plan.During his first five years with CBS Interactive, Chris started his journalistic advent...

3 comments
Ekendra Lamsal
Ekendra Lamsal

This is really awesome guide. Before I planned for implementing third party login solutions - I usually do a research on user part. And never ended up with Twitter until then. Now, the time is to be changed. Thanks for the codes part as well.

spdragoo
spdragoo

While this undoubtedly does save the programmer some time & effort, as well as pawning the responsibility for privacy of user information onto the other service, it makes 2 huge assumptions: 1) That the people who want to use your application have a Twitter (or other social website) account, or are willing to set up said account [b]just for the privilege of using your app[/b]; and 2) They're willing to use their Twitter (or other account) to use your app. Sure, it's not that big of an issue if the web developer is trying to come up with the next social media app, because the assumption is that you're targeting the social media users & wanting your app to be found on that particular site. However, this article seems to be aimed more at "independent" web developers...i.e. those trying to develop a site that provides unique content for the users, but not necessarily tied into a particular social media site. And given the privacy concerns that certain sites (*cough* Facebook *cough*) have had recently, even people with social media accounts may not want to have their social media account tied into a non-affiliated website. Or, to look at it another way... why would I sign up for a message board where people can discuss FPS games for PCs, when it wants me to use my Yahoo email username/password to log in? There's no need to tie them together -- especially when it would have been easier for the website creator to simply create a Yahoo! Groups site (thereby justifying the use of my Yahoo ID) since it allows them to control who joins the group.

CharlieSpencer
CharlieSpencer

"When starting out, using another site???s online identity is a good way to sidestep the issue, and get to coding what really matters." I interpreted that to mean using a third-party validation for development and testing, not for production.

Editor's Picks