PHP - - By Peter Nijssen

Social Network Authentication: Twitter and Facebook

Using Social Networks as a Login System

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.

Twitter authentication

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:

  • Client id
  • key
  • callback URL

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 OAuth\ServiceFactory;
use OAuth\OAuth1\Service\Twitter;
use OAuth\Common\Storage\Session;
use OAuth\Common\Consumer\Credentials;

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. 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 user

Twitter returns the data back in a different way than Google+, so we have to normalize again. 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 SitePoint\SocialLogin\Twitter;

use SitePoint\SocialLogin\Interfaces\SocialUserInterface;

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.

Facebook login

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 SitePoint\SocialLogin\Facebook;

use SitePoint\SocialLogin\Interfaces\SocialLoginInterface;
use OAuth\ServiceFactory;
use OAuth\OAuth2\Service\Facebook;
use OAuth\Common\Storage\Session;
use OAuth\Common\Consumer\Credentials;

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.

Facebook user

The user class is even easier and doesn’t contain any surprises. When done, it should look like this.

<?php

namespace SitePoint\SocialLogin\Facebook;

use SitePoint\SocialLogin\Interfaces\SocialUserInterface;

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;
    }
}

Quick test

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.

Conclusion

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. With this article, we’ve come to the end of this series. Did it give you some insight in how to deal with social networks or framework agnostic packages? Feel free to ask any questions in the comments below!

Sponsors