cult3

Adding Social Authentication to a Laravel 4 application Part 2

Jun 02, 2014

Table of contents:

  1. Collecting additional information from the user
  2. Collect the data to create a new user
  3. Creating the SocialProviderRegistrator
  4. Inject the service into the Controller
  5. Conclusion

Last week I looked at authenticating a user through a social provider. In my example I was using Twitter, but there are many different social authentication providers available that all use the common protocol of Oauth.

In last week’s tutorial I left it at the stage where we have successfully authenticated a user through a social provider and we now have an object that contains some limited data about the user.

In this week’s tutorial I’m going to finish off the registration process by implementing a new child class of the registrator service from a couple of weeks ago.

Collecting additional information from the user

When you retrieve user data from the Twitter api, you will only be returned data that is already publicly available. This means you won’t automatically get the user’s email address.

In order to get the user’s email address, you have to request it for yourself as part of your application’s registration process.

So once the user has successfully authenticated via Twitter and I have collected the user’s oauth_token and oauth_token_secret, I’m going to redirect the user to a form so they can finish off registering for Cribbb.

The first thing to do is to create a new route and add the redirect to the callback() method on the AuthenticateController from last week:

Route::get("auth/register", [
    "uses" => "AuthenticateController@register",
    "as" => "authenticate.register",
]);

Note: ensure that you put this route ahead of the auth/{provider} route in your routes.php file!

The final callback() method should look like this:

/**
 * 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();

        return Redirect::route('authenticate.register');
    } catch(Exception $e) {
        return App::abort(404);
    }
}

Next I need to create the register() method for returning the correct view:

/**
 * Return the form so the user can complete their registration
 *
 * @return View
 */
public function register()
{
    return View::make('authenticate.register', ['username' => Session::get('username')]);
}

In this method I’m also passing the username from the Session. This will allow me to auto populate the form with the user’s Twitter username, however they will also be able to choose a different username by updating the form.

And finally, the view file should look something like this:

{{ Form::open(array('route' => 'authenticate.store')) }}

@if ($errors->any())
<ul>
{{ implode(",", $errors->all('<li>:message</li>'))}}
</ul>
@endif

<div>
{{ Form::label('email', 'Email Address') }}
{{ Form::text('email') }}
</div>

<div>
{{ Form::label('username', 'Username') }}
{{ Form::text('username', $username) }}
</div>

{{ Form::submit('Complete') }}

{{ Form::close() }}

This view file is just your basic Laravel form. Here I’m requesting the user’s username and email address. I don’t need the user’s password because that’s kinda the point of using a social authentication provider.

Collect the data to create a new user

Next I need to create the route that will accept the POST request from this form:

Route::post("auth", [
    "uses" => "AuthenticateController@store",
    "as" => "authenticate.store",
]);

In order to create the new user, I will need to gather data from both the POST request as well as the Session.

To do this I will create a new store() method on the AuthenticateController to accept the POST request:

/**
 * Store the user's details and authenticate on success
 *
 * @return Redirect
 */
public function store()
{
    $data = [
        'username' => Input::get('username'),
        'email' => Input::get('email'),
        'oauth_token' => Session::get('oauth_token'),
        'oauth_token_secret' => Session::get('oauth_token_secret')
    ];
}

The first thing to do in this method is to create a $data array of the different bits of data from the Input and the Session.

Creating the SocialProviderRegistrator

In the first tutorial on creating a registration process, I created a CredentialsRegistrator to register new users with a username, email and password.

To register a user with social provider credentials instead, all I have to do is to create a new child Registrator class called SocialProviderRegistrator:

<?php namespace Cribbb\Registrators;

use Cribbb\Repositories\User\UserRepository;

class SocialProviderRegistrator extends AbstractRegistrator implements
    Registrator
{
    /**
     * The UserRepository
     *
     * @param Cribbb\Repositories\User\UserRepository
     */
    protected $userRepository;

    /**
     * An array of Validable classes
     *
     * @param array
     */
    protected $validators;

    /**
     * Create a new instance of the CredentialsRegistrator
     *
     * @return void
     */
    public function __construct(
        UserRepository $userRepository,
        array $validators
    ) {
        parent::__construct();

        $this->userRepository = $userRepository;
        $this->validators = $validators;
    }

    /**
     * Create a new user entity
     *
     * @param array $data
     * @return Illuminate\Database\Eloquent\Model
     */
    public function create(array $data)
    {
        if ($this->runValidationChecks($data)) {
            return $this->userRepository->create($data);
        }
    }
}

This class is essentially the same as the CredentialsRegistrator class as the registration process is really quite simple at this stage. Separating the two processes into two different service classes gives us future scope for allowing the two registration processes to diverge.

Registration with a social provider requires that an oauth_token and oauth_token_secret are present in the user’s data. To ensure that the data passed to the create() method meets this requirement, we can create a new Validator class:

<?php namespace Cribbb\Registrators\Validators;

use Cribbb\Validators\Validable;
use Cribbb\Validators\LaravelValidator;

class OauthTokenValidator extends LaravelValidator implements Validable
{
    /**
     * Validation rules
     *
     * @var array
     */
    protected $rules = [
        "oauth_token" => "required",
        "oauth_token_secret" => "required",
    ];
}

Finally, the SocialProviderRegistrator can be bound to the IoC container in the RegistratorsServiceProvider:

/**
 * Register the CredentialsRegistrator service
 *
 * @return void
 */
public function registerSocialProviderRegistrator()
{
    $this->app->bind('Cribbb\Registrators\SocialProviderRegistrator', function($app) {
        return new SocialProviderRegistrator(
            $this->app->make('Cribbb\Repositories\User\UserRepository'),
            [
            new UsernameValidator($app['validator']),
            new EmailValidator($app['validator']),
            new OauthTokenValidator($app['validator'])
            ]
        );
    });
}

In this example I’m reusing the UsernameValidator and the EmailValidator, but I’m also injecting the OauthTokenValidator too.

Inject the service into the Controller

With the SocialProviderRegistrator service created we can now inject it into the AuthenticateController:

use Cribbb\Authenticators\Manager;
use Cribbb\Registrators\SocialProviderRegistrator;

class AuthenticateController extends BaseController {

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

/**
 * The Registrator instance
 *
 * @param Cribbb\Registrators\SocialProviderRegistrator
 */
protected $registrator;

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

Finally, we can now use the $this->register instance to create a new user:

/**
 * Store the user's details and authenticate on success
 *
 * @return Redirect
 */
public function store()
{
    $data = [
        'username' => Input::get('username'),
        'email' => Input::get('email'),
        'oauth_token' => Session::get('oauth_token'),
        'oauth_token_secret' => Session::get('oauth_token_secret')
    ];

    $user = $this->registrator->create($data);

    if ($user) {
        Auth::login($user);

        return Redirect::route('home.index');
    }

    return Redirect::route('authenticate.register')->withInput()
        ->withErrors($this->registrator->errors());
}

If a new user is successfully created we can authenticate them and redirect to the main page of the application. If the user’s data is invalid we can redirect back to the form with the data and an array of errors to be displayed that will allow the user to correct their mistakes or try again with different data.

Conclusion

In this tutorial we’ve built upon the foundation from the last couple of weeks to create a process for authenticating with a social provider and registering a user within our own application.

This registration process is greatly simplified because we’ve already done the hard work by building the flexible registration process for registering new users with username and password credentials. This means to add subsequent different types of registration processes, all we need to do is to create the bits that are relevant, rather than reinventing the wheel each time. We can also leverage the same validator classes so we don’t have to duplicate code for each type of registration process.

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.