May 26, 2014
Table of contents:
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.
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.
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.
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"
}
}
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.
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.
So as a quick recap, the process for someone authenticating with your application will go like this:
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.
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).
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.
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
.
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;
}
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.
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);
}
}
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.