This article is written for and published on SitePoint.
In the previous parts of this series, we created our initial interfaces, set up our Google+ login functionality and talked about how we can merge our accounts together. In this article, we will integrate Twitter and Facebook within our application. You will see a lot of similarities with the Google+ article, so if you could follow that one easily, you won’t have much trouble with this one. If you haven’t read that article yet, I suggest you read it first before continuing this article.
You can view the code for all the articles on this Github page.
Once again, we start off by creating the following directory: src/SitePoint/SocialLogin/Twitter
. Within this directory, we create the TwitterLogin
class. This class implements the SocialLoginInterface
interface which we created in the first article. Make sure a property called service
is available to store our service in.
Like Google+, Twitter has some specific needs to make sure we can log in. So make sure we have the following 3 properties present in the class:
Since all 3 properties are required for our application to work, our constructor will receive these 3 variables as parameters and set the properties.
Up until now, your code should look the same as the first example in the Google+ article.
Our next step is to create our Twitter service. We will be using the same OAuth library and will set up a connection within the init method.
Before we can start, we have to add some use statements to the top of our class.
use OAuthServiceFactory;
use OAuthOAuth1ServiceTwitter;
use OAuthCommonStorageSession;
use OAuthCommonConsumerCredentials;
Everything for our init method is now present. Time to set up our connection with Twitter. We do this with the basic examples of the OAuth library we are using.
/**
* Initializes our service
*/
public function init()
{
$storage = new Session();
$serviceFactory = new ServiceFactory();
$credentials = new Credentials($this->clientId, $this->key, $this->callbackUrl);
$this->service = $serviceFactory->createService('twitter', $credentials, $storage);
return $this;
}
Our service is set now, so we can now continue by filling out other methods from the interface. We will start off with the getLoginUrl
method. This method will return a URL which you will be redirecting your user to.
/**
* Returns the login url for the social network
*
* @return string
*/
public function getLoginUrl()
{
$token = $this->service->requestRequestToken();
return $this->service->getAuthorizationUri(array('oauth_token' => $token->getRequestToken()));
}
You might notice that this method (and the following one which we will discuss) is slightly different from the Google+ article. This is due to the fact that Google+ uses OAuth 2, while Twitter uses OAuth 1. Twitter was one of the first companies to use OAuth, since the original creator of OAuth was working at Twitter at that time. Till now Twitter is still using OAuth 1, although they are making progress moving to OAuth 2.
The next method to define is the loginCallback
method. When a user accepts twitter sharing data with your application, the user will be redirected back to the URL you defined as the callbackUrl
. When the user is requesting that URL, you should call the loginCallback
method from this class.
If everything went well, we can now retrieve data from Twitter.
/**
* Handles the login callback from the social network
*
* @return SocialUserInterface
*/
public function loginCallback()
{
$storage = new Session();
$token = $storage->retrieveAccessToken('Twitter');
$this->service->requestAccessToken(
$_GET['oauth_token'],
$_GET['oauth_verifier'],
$token->getRequestTokenSecret()
);
$userData = json_decode($this->service->request('account/verify_credentials.json'), true);
$twitterUser = new TwitterUser($userData);
return $twitterUser;
}
This time we are using a class named TwitterUser
here and we are returning a class with the interface SocialUserInterface
. However, this class is not yet present. Time to create it!
Twitter returns the data back in a different way then Google+. So again, we have to normalize our data. We need to create a class named TwitterUser
which will implement the SocialUserInterface
we created in the first article.
The constructor of this class expects the raw data of Twitter in this case. In every getter, we will retrieve the data we want from the raw data. In the end, your TwitterUser
class could look like this.
<?php
namespace SitePointSocialLoginTwitter;
use SitePointSocialLoginInterfacesSocialUserInterface;
class TwitterUser implements SocialUserInterface {
/**
* @var mixed user data
*/
private $userData;
/**
* Constructor.
*
* @param $userData mixed Raw social network data for this particular user
*/
public function __construct($userData)
{
$this->userData = $userData;
}
/**
* Get the provider name
*
* @return string
*/
public function getProvider()
{
return "twitter";
}
/**
* Get the UID of the user
*
* @return string
*/
public function getUid()
{
if(array_key_exists('id', $this->userData)) {
return $this->userData['id'];
}
return null;
}
/**
* Get the first name of the user
*
* @return string
*/
public function getFirstname()
{
if(array_key_exists('name', $this->userData)) {
list($firstname, $lastname) = explode(" ", $this->userData['name']); // TODO: Of course you have to make this smarter
return $firstname;
}
return null;
}
/**
* Get the last name of the user
*
* @return string
*/
public function getLastname()
{
if(array_key_exists('name', $this->userData)) {
list($firstname, $lastname) = explode(" ", $this->userData['name']); // TODO: Of course you have to make this smarter
return $lastname;
}
return null;
}
/**
* Get the username
*
* @return string
*/
public function getUsername()
{
if(array_key_exists('screen_name', $this->userData)) {
return $this->userData['screen_name'];
}
return null;
}
/**
* Get the emailaddress
*
* @return string
*/
public function getEmailAddress()
{
return null;
}
/**
* Get the city
*
* @return string
*/
public function getCity()
{
if(array_key_exists('location', $this->userData)) {
return $this->userData['location'];
}
return null;
}
/**
* Get the birthdate
*
* @return string
*/
public function getBirthDate()
{
return null;
}
/**
* Get the gender
*
* @return string
*/
public function getGender()
{
return null;
}
}
You might notice that we have a lot less information than with Google+. Twitter just doesn’t share as much info. For instance, we are missing the email address, while this is one of the most common fields you would like to have within your application.
The only thing you can do is, after a login with Twitter, ask the user to fill in his email address and save it to your database. Other than that, you can try to merge a Twitter account with an already given account like in the previous part of this series.
With the knowledge you gathered from the Google+ and Twitter explanation, it should be easy for you to set the Facebook version up. Here you have the full implementation for the login class.
<?php
namespace SitePointSocialLoginFacebook;
use SitePointSocialLoginInterfacesSocialLoginInterface;
use OAuthServiceFactory;
use OAuthOAuth2ServiceFacebook;
use OAuthCommonStorageSession;
use OAuthCommonConsumerCredentials;
class FacebookLogin implements SocialLoginInterface {
/**
* Facebook service
*
* @var string
*/
protected $service;
/**
* OAuth client ID
*
* @var string
*/
protected $clientId;
/**
* OAuth key
*
* @var string
*/
protected $key;
/**
* Callback url
*
* @var string
*/
protected $callbackUrl;
/**
* Constructor.
*
* @param $clientId string
* @param $key string
* @param $callbackUrl string
*/
public function __construct($clientId, $key, $callbackUrl)
{
$this->clientId = $clientId;
$this->key = $key;
$this->callbackUrl = $callbackUrl;
}
/**
* Initializes our service
*/
public function init()
{
$storage = new Session();
$serviceFactory = new ServiceFactory();
$credentials = new Credentials($this->clientId, $this->key, $this->callbackUrl);
$this->service = $serviceFactory->createService(
'facebook',
$credentials,
$storage,
array(
Facebook::SCOPE_EMAIL,
Facebook::SCOPE_USER_BIRTHDAY,
Facebook::SCOPE_USER_LOCATION
)
);
return $this;
}
/**
* Returns the login url for the social network
*
* @return string
*/
public function getLoginUrl()
{
return $this->service->getAuthorizationUri();
}
/**
* Handles the login callback from the social network
*
* @param string $accessCode
*
* @return SocialUserInterface
*/
public function loginCallback($accessCode)
{
$token = $this->service->requestAccessToken($accessCode);
// Send a request with it
$userData = json_decode($this->service->request('/me'), true);
$facebookUser = new FacebookUser($userData);
return $facebookUser;
}
}
What you might notice is that I defined some scopes when creating the Facebook service. By defining these scopes, I can get back some extra details from the user. In this case, I am also collecting the email address, birthday and location. Do note that the more data you gather, the more chance you have a user will refuse to log in with your app.
The user class is even easier and doesn’t contain any surprises. When done, it should look like this.
<?php
namespace SitePointSocialLoginFacebook;
use SitePointSocialLoginInterfacesSocialUserInterface;
class FacebookUser implements SocialUserInterface {
/**
* @var mixed user data
*/
private $userData;
/**
* Constructor.
*
* @param $userData mixed Raw social network data for this particular user
*/
public function __construct($userData)
{
$this->userData = $userData;
}
/**
* Get the provider name
*
* @return string
*/
public function getProvider()
{
return "facebook";
}
/**
* Get the UID of the user
*
* @return string
*/
public function getUid()
{
if(array_key_exists('id', $this->userData)) {
return $this->userData['id'];
}
return null;
}
/**
* Get the first name of the user
*
* @return string
*/
public function getFirstname()
{
if(array_key_exists('first_name', $this->userData)) {
return $this->userData['first_name'];
}
return null;
}
/**
* Get the last name of the user
*
* @return string
*/
public function getLastname()
{
if(array_key_exists('last_name', $this->userData)) {
return $this->userData['last_name'];
}
return null;
}
/**
* Get the username
*
* @return string
*/
public function getUsername()
{
if(array_key_exists('name', $this->userData)) {
return str_replace(" ", "_", $this->userData['name']);
}
return null;
}
/**
* Get the emailaddress
*
* @return string
*/
public function getEmailAddress()
{
if(array_key_exists('email', $this->userData)) {
return $this->userData['email'];
}
return null;
}
/**
* Get the city
*
* @return string
*/
public function getCity()
{
if(array_key_exists('location', $this->userData)) {
return $this->userData['location'];
}
return null;
}
/**
* Get the birthdate
*
* @return string
*/
public function getBirthDate()
{
if(array_key_exists('birthday', $this->userData)) {
return $this->userData['birthday'];
}
return null;
}
/**
* Get the gender
*
* @return string
*/
public function getGender()
{
if(array_key_exists('location', $this->userData)) {
return $this->userData['location']['name'];
}
return null;
}
}
If you want to run a quick test to try out the code, clone the Github repository to your local computer and switch to the branch named part4
. Within this branch, you will see a testTwitter.php
and testFacebook.php
file. You can fill in the API details from both services and run the file within your browser. When requesting the page, you will be redirected to the social media page, requesting you to share information.
When clicking accept, you will be redirected back to the page you configured as callback URL, showing your first and last name.
In this article we took the base we set in the previous articles and worked further on that. Next to Google+, we can now also log in with Twitter and Facebook. In all cases, we return a normalized user object, which we can add to our own user system.