cult3

Adding Social Authentication to a Laravel 4 application Part 1

May 26, 2014

Table of contents:

  1. How does Social Authentication work?
  2. Register a new application with Twitter
  3. Installing an Oauth 1.0 client
  4. Configuration
  5. Add the token and secret to the User model
  6. The required routes
  7. Create the Authenticate Controller
  8. Create the Manage class
  9. Authenticators Service Provider
  10. Inject the Manager class into the Controller
  11. Making a request to Twitter
  12. Handling the callback
  13. Conclusion

A popular form of authentication for modern consumer web applications is using an existing social service to reduce the friction to potential new users. Offering a popular existing authentication service through a website such as Twitter reduces the mental block of signing up for yet another new application. What’s more, if the user is already signed in to the other service, signing up to your application will only be a couple of clicks.

This type of third-party authentication has been around for a couple of years now. It is also pretty uncommon to see a brand new consumer application without some kind of existing social sign in. This is a big benefit to us because we can use the existing patterns to make our lives a whole lot easier.

In this tutorial I’m going to be looking at setting up authentication using Twitter.

How does Social Authentication work?

Before I jump into the code I’ll explain how social authentication works. It can be a difficult to get your head around what’s going on at first, so I think it’s important to understand what’s going on under the hood.

When a user clicks the “Sign in with Twitter” button they will be taken to a page on twitter.com that will allow them to grant access to their account via your web application. This will allow you to perform certain actions on the user’s Twitter account depending on the scope of the access.

Instead of the user divulging their Twitter email address and password to you, you are given a special access token that allows you to interact with Twitter on the user’s behalf. This protects the user’s Twitter credentials and allows Twitter to scope the access that you have to the user’s Twitter account.

This process is known as Oauth and it is a common open standard for authorisation on the Internet. Many of the biggest applications use Oauth in one form or another. Twitter for example use the Oauth 1.0 specification.

However you don’t need to understand the intricacies of Oauth to understand what is going on when you authorise via a third-party service. All you need to know is that you ask your user to grant access, you then receive a special token that you can use to interact with the user’s account.

Register a new application with Twitter

Before we write any code, first we have to register our application with Twitter. In order to make requests we need our own api key and secret.

Go to https://dev.twitter.com and create a new application. You will be required to enter some details about your application, but you can come back and fill it in properly at some point in the future.

When creating a new Twitter application there are a few things to note. Firstly you can specify an access level. I’ve chosen read only because I don’t need to do anything special with the user’s account. Generally speaking you should try and set this to be as low as possible. User’s don’t like giving full access to third party services.

Secondly, you will be given an api key and secret. Make a note of these because you will need to store them as configuration details in your application.

And thirdly you will need to specify a callback url. This is the url that the user will be sent to after they have authorised your application. I’ve set mine to https://cribbb.dev/auth/twitter/callback but you are free to choose whatever you want.

Installing an Oauth 1.0 client

As I mentioned at the top of the page, connecting your web application to a third party services isn’t exactly a new thing. There is now a great selection of open source components that we can drop in to our applications to make working with these services easier.

I’m a big fan of not reinventing the wheel so I will jump at the opportunity to use a trusted and battle-hardened open source library. Oauth is a recognised open standard so there are plenty of open sourced libraries to choose from.

When choosing an Oauth package, you need to ensure that you choose one that is applicable to the specification that you want to work with. Twitter still uses Oauth 1.0 so we have to use a package that supports this version.

I’m going to be using this package. If you want to use Facebook or many of the other social authentication services out there you will probably need Oauth 2.0 and therefore you should use this package.

To install the Oauth client into your application, add the following to your composer.json file:

{
    "require": {
        "league/oauth1-client": "~1.0"
    }
}

Configuration

A couple of weeks ago I looked at dealing with configuration details in your application. You will probably end up having to deal with a lot of separate configuration details as your web application increases in complexity.

In order to connect and authorise with Twitter’s API you will need to use the API key and secret that you were given when you registered your application. This is a good opportunity to set up the local configuration to keep those details out of your version control.

The first thing to do is to create a file called .env.local.php and add it to your .gitignore.

Next copy the following and replace with the details of your application:

return [
    "TWITTER_IDENTIFIER" => "...",
    "TWITTER_SECRET" => "...",
    "TWITTER_CALLBACK_URI" => "...",
];

Next we need to add the configuration details to Laravel’s configuration. You can either create a new file for this or add it to an existing file. In my case I’m just going to add it to the existing auth.php file under app/config.

At the bottom of the file I’ve added another section for providers:

'providers' => array(
    'twitter' => array(
        'identifier' => $_ENV['TWITTER_IDENTIFIER'],
        'secret' => $_ENV['TWITTER_SECRET'],
        'callback_uri' => $_ENV['TWITTER_CALLBACK_URI']
    )
)

Now when we need to access those details we can do so through Laravel’s configuration class, but we don’t have to keep those private details within the application’s git repository.

Add the token and secret to the User model

When a user successfully authenticates with our application through a social provider, we will receive a special token and secret that can be used to make requests on behalf of the user. This token and secret combination is very important, and so we will need to save them into the database.

Add the following two columns to your users table migration:

$table->string("oauth_token")->nullable();
$table->string("oauth_token_secret")->nullable();

Also add them to the $fillable property on the User model if you want to be able to mass assign them.

I’ve set these two columns to nullable as I will also be offering new users the option to just create a normal account rather than using Twitter.

The required routes

So as a quick recap, the process for someone authenticating with your application will go like this:

  1. The user is taken from your application to Twitter
  2. The user authenticates with Twitter and authorises your application
  3. The user is returned to your application with their secret token

So we need to create step 1, a way to direct a user to Twitter with our application’s credentials and step 3, a way to accept a request from Twitter with the user’s token.

The first thing I will do is to define a couple of routes:

/**
 * Social Authentication
 *
 * Authenticate via a social provider
 */
Route::get("auth/{provider}", [
    "uses" => "AuthenticateController@authorise",
    "as" => "authenticate.authorise",
]);
Route::get("auth/{provider}/callback", [
    "uses" => "AuthenticateController@callback",
    "as" => "authenticate.callback",
]);

The auth/{provider} route will be the route that the user is taken to when they click the Authenticate with Twitter button and the auth/{provider}/callback route will be where the user is redirected back to once they have authenticated with the third-party provider.

Notice how I’ve also created a new AuthenticateController rather than putting this in the RegisterController from last week. This is a different type of registration process, so I think it deserves it’s own controller.

Create the Authenticate Controller

The next thing to do is to create the AuthenticateController:

class AuthenticateController extends BaseController
{
    /**
     * Create a new instance of the AuthenticateController
     *
     * @return void
     */
    public function __construct()
    {
        $this->beforeFilter("invite");
    }
}

The only thing to note here is that I’m including the invite filter from last week to ensure only user’s with a valid invitation can authenticate (and therefore register as a new user).

Create the Manage class

In this tutorial I’m focusing on only authenticating with Twitter. However, writing code that would handcuff me to only ever use Twitter as a third-party authentication provider would probably be a bad idea.

Instead it is much better to enable multiple providers and treat them as generic provider instances.

In order to do that I’m going to create a very simple Manager class that will hold each provider instance. I will then inject this Manager class into the AuthenticateController to resolve an instance of the required provider.

Note: I’m not sure if “Manager” is the correct technical name for this class. I guess it’s more of a “Factory” than a manager, but what’s in a name, eh?

I’m going to house this Manager class under the namespace of Cribbb\Authenticators:

<?php namespace Cribbb\Authenticators;

use Exception;

class Manager
{
    /**
     * Authentication providers
     *
     * @var array
     */
    protected $providers = [];

    /**
     * Check to see if a provider has been set
     *
     * @param string $name
     * @return bool
     */
    public function has($name)
    {
        if (isset($this->providers[strtolower($name)])) {
            return true;
        }

        return false;
    }

    /**
     * Set a new authentication provider
     *
     * @return void
     */
    public function set($name, $provider)
    {
        $this->providers[strtolower($name)] = $provider;
    }

    /**
     * Get an existing authentication provider
     *
     * @return League\OAuth1\Client\Server
     */
    public function get($name)
    {
        if ($this->has(strtolower($name))) {
            return $this->providers[strtolower($name)];
        }

        throw new Exception("$name is not a valid property");
    }
}

There’s nothing really exciting about this class. All it has is a method for setting an instance, getting an instance and checking if an instance is set. Each provider instance will be saved into the $providers array.

Authenticators Service Provider

The next thing I need to do is to create an AuthenticatorsServiceProvider to set up the Manager class with the authentication providers that I want to support:

<?php namespace Cribbb\Authenticators;

use Illuminate\Support\ServiceProvider;

class AuthenticatorsServiceProvider extends ServiceProvider
{
    /**
     * Register each authentication provider
     *
     * @return void
     */
    public function register()
    {
    }
}

The first thing I will do is to register the Manager class:

/**
 * Register each authentication provider
 *
 * @return void
 */
public function register()
{
    $this->registerManager();
}

/**
 * Register the Manager class
 *
 * @return void
 */
public function registerManager()
{
    $this->app['auth.providers.manager'] = $this->app->share(function($app) {
        return new Manager;
    });

    $this->app->bind('Cribbb\Authenticators\Manager', function($app) {
        return $app['auth.providers.manager'];
    });
}

Next I will register the Twitter object from the Oauth package I installed earlier:

/**
 * Register each authentication provider
 *
 * @return void
 */
public function register()
{
    $this->registerManager();
    $this->registerTwitterAuthenticator();
}

/**
 * Register Twitter Authenticator
 *
 * @return void
 */
public function registerTwitterAuthenticator()
{
    $this->app['auth.providers.twitter'] = $this->app->share(function($app) {
        return new Twitter(array(
            'identifier' => $app['config']->get('auth.providers.twitter.identifier'),
            'secret' => $app['config']->get('auth.providers.twitter.secret'),
            'callback_uri' => $app['config']->get('auth.providers.twitter.callback_uri')
        ));
    });
}

Notice how I’m injecting the configuration details that we set up at the beginning of this tutorial.

Finally, in the boot() method I will inject the Twitter provider into the Manager class:

/**
 * Inject the provders into the Manager class
 *
 * @return void
 */
public function boot()
{
    $this->app['auth.providers.manager']->set('twitter', $this->app['auth.providers.twitter']);
}

The entire AuthenticateSerivceProvider class looks like this:

<?php namespace Cribbb\Authenticators;

use Illuminate\Support\ServiceProvider;
use League\OAuth1\Client\Server\Twitter;

class AuthenticatorsServiceProvider extends ServiceProvider
{
    /**
     * Register each authentication provider
     *
     * @return void
     */
    public function register()
    {
        $this->registerManager();
        $this->registerTwitterAuthenticator();
    }

    /**
     * Inject the provders into the Manager class
     *
     * @return void
     */
    public function boot()
    {
        $this->app["auth.providers.manager"]->set(
            "twitter",
            $this->app["auth.providers.twitter"]
        );
    }

    /**
     * Register the Manager class
     *
     * @return void
     */
    public function registerManager()
    {
        $this->app["auth.providers.manager"] = $this->app->share(function (
            $app
        ) {
            return new Manager();
        });

        $this->app->bind("Cribbb\Authenticators\Manager", function ($app) {
            return $app["auth.providers.manager"];
        });
    }

    /**
     * Register Twitter Authenticator
     *
     * @return void
     */
    public function registerTwitterAuthenticator()
    {
        $this->app["auth.providers.twitter"] = $this->app->share(function (
            $app
        ) {
            return new Twitter([
                "identifier" => $app["config"]->get(
                    "auth.providers.twitter.identifier"
                ),
                "secret" => $app["config"]->get(
                    "auth.providers.twitter.secret"
                ),
                "callback_uri" => $app["config"]->get(
                    "auth.providers.twitter.callback_uri"
                ),
            ]);
        });
    }
}

Finally add AuthenticatorsServiceProvider to your list of providers under app/config/app.php.

Inject the Manager class into the Controller

Now that we’ve got the Manager class set up, we can inject it into the AuthenticateController:

use Cribbb\Authenticators\Manager;

class AuthenticateController extends BaseController {

/**
 * The Provider Manager instance
 *
 * @param Cribbb\Authenticators\Manager
 */
protected $manager;

/**
 * Create a new instance of the AuthenticateController
 *
 * @param Cribbb\Authenticators\Manager
 * @return void
 */
public function __construct(Manager $manager)
{
    $this->beforeFilter('invite');
    $this->manager = $manager;
}

Making a request to Twitter

The first method to tackle in the AuthenticateController is the authorise() method. This method will be where the user is taken when she clicks on the Authenticate with Twitter button:

/**
 * Authorise an authentication request
 *
 * @return Redirect
 */
public function authorise($provider)
{
    try {
        $provider = $this->manager->get($provider);
    }

    catch(Exception $e) {
        return App::abort(404);
    }
}

The first thing to do is to resolve the correct provider from the Manager class. Because we are accepting the $provider through the URL, we need to deal with situations where the provider doesn’t actually exist.

When a provider does not exist in the manager class, the class will throw an exception. This means we can simply catch the exception and return a 404 not found exception.

When your application makes a request to Twitter, first we need to get some temporary credentials in order to make the authorisation. Fortunately the Twitter package we installed deals with all of this in the background:

/**
 * Authorise an authentication request
 *
 * @return Redirect
 */
public function authorise($provider)
{
    try {
        $provider = $this->manager->get($provider);

        $credentials = $provider->getTemporaryCredentials();
    } catch(Exception $e) {
        return App::abort(404);
    }
}

In the method above, the provider will make the request for the temporary credentials and return them to the $credentials variable.

Next we need to save the credentials in the session so we can verify the callback from Twitter:

/**
 * Authorise an authentication request
 *
 * @return Redirect
 */
public function authorise($provider)
{
    try {
        $provider = $this->manager->get($provider);

        $credentials = $provider->getTemporaryCredentials();

        Session::put('credentials', $credentials);
        Session::save();

        return $provider->authorize($credentials);
    } catch(Exception $e) {
        return App::abort(404);
    }
}

Notice how I call the save method on the Session class? This is because we want to force the session to save before the user is redirected to twitter.com.

Finally I call the authorize() method on the $provider instance and pass my temporary credentials. This will automatically redirect the browser to twitter.com so the user can authenticate.

Handling the callback

When the user authenticates with Twitter they will be redirected back to your callback URL with a oauth_token and a oauth_verifier as part of the GET request.

In order to get details about the user we need to use these credentials along with the temporary credentials from the initial request to get a token. Again making these requests and swapping the tokens will all abstracted inside of the Oauth library so we don’t have to worry about any of that.

First, we resolve an instance of the provider in the same way as the last method by catching exceptions when an invalid provider was requested:

/**
 * Receive the callback from the authentication provider
 *
 * @return Redirect
 */
public function callback($provider)
{
    try {
        $provider = $this->manager->get($provider);
    } catch(Exception $e) {
        return App::abort(404);
    }
}

Next we can use the oauth_token and the oauth_verifier from the request from Twitter, as well as the temporary credentials that are saved in the session to request a $token from Twitter:

/**
 * Receive the callback from the authentication provider
 *
 * @return Redirect
 */
public function callback($provider)
{
    try {
        $provider = $this->manager->get($provider);

        $token = $provider->getTokenCredentials(
            Session::get('credentials'),
            Input::get('oauth_token'),
            Input::get('oauth_verifier')
        );
    } catch(Exception $e) {
        return App::abort(404);
    }
}

The $token that you are returned has the user’s identifier and secret. These two credentials are what you need to make requests to the Twitter API on behalf of the user. To access these credentials you can use the following two methods:

$token->getIdentifier();
$token->getSecret();

The Oauth package we’ve been using also allows you to get some limited information about the user from the Twitter API. For example, try using the following code to get some basic user details:

$user = $provider->getUserDetails($token);

You will be returned an instance of League\OAuth1\Client\Server\User that you can use to work with the user’s details.

Finally I will save the username, oauth_token and oauth_token_secret into the session so I can use them to create the user’s account in Cribbb:

/**
 * Receive the callback from the authentication provider
 *
 * @return Redirect
 */
public function callback($provider)
{
    try {
        $provider = $this->manager->get($provider);

        $token = $provider->getTokenCredentials(
            Session::get('credentials'),
            Input::get('oauth_token'),
            Input::get('oauth_verifier')
        );

        $user = $provider->getUserDetails($token);

        Session::put('username', $user->nickname);
        Session::put('oauth_token', $token->getIdentifier());
        Session::put('oauth_token_secret', $token->getSecret());
        Session::save();

        // Redirect to account completion
    } catch(Exception $e) {
        return App::abort(404);
    }
}

Conclusion

In this tutorial we’ve looked at authenticating a user through as social provider such as Twitter. This is such a common thing for new consumer applications, it would be a bit weird not to offer this option.

The tutorial used a “Manager” class to provide a common way of resolving any number of providers that can be used to authenticate a user. If you wanted to offer another provider that also used Oauth 1.0, all you would have to do would be to inject the provider instance into the Manager class.

If you wanted to also offer authentication through Facebook or any number of other providers that use the Oauth 2.0 specification, you would need a package such as this.

However because Oauth 1.0 and Oauth 2.0 use slightly different processes for authentication, you would need to factor this into your code.

I may offer Oauth 2.0 provider authentication in a future tutorial, but for now I’m not going to over-complicate things at this stage.

Next week we’ll continue looking at registering new users through a social authentication provider.

This is a series of posts on building an entire Open Source application called Cribbb. All of the tutorials will be free to web, and all of the code is available on GitHub.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.