Step by Step Laravel Social Login Authentication

Are you looking for a simple and easy to maintain social login authentication code for your Laravel application? Well, this step by step tutorial might help you to solve that problem. Let's get started!
Laravel Socialite Package Installation
First, we need to install the Laravel Socialite package:composer require laravel/socialite
After installing the Laravel Socialite package, add the
Laravel\Socialite\SocialiteServiceProvider
in your config/app.php
configuration file:'providers' => [
// Other service providers...
Laravel\Socialite\SocialiteServiceProvider::class,
],
In the same file
config/app.php
, add the Socialite
facade to the aliases
array.'Socialite' => Laravel\Socialite\Facades\Socialite::class,
Next, you need to add your credentials for the OAuth services your application wish to utilize. These credentials should be placed in your
config/services.php
configuration file, and should use the key facebook
, twitter
, linkedin
, google
, github
or bitbucket
, depending on the providers your application requires:'facebook' => [
'client_id' => env('FACEBOOK_CLIENT_ID'),
'client_secret' => env('FACEBOOK_CLIENT_SECRET'),
'redirect' => env('FACEBOOK_CLIENT_CALLBACK')
],
Your .env
file might look like this:FACEBOOK_CLIENT_ID=your_facebook_app_id
FACEBOOK_CLIENT_SECRET=your_facebook_app_secret
FACEBOOK_CLIENT_CALLBACK=http://yourdomain.com/auth/facebook/callback
Below is some of the links on how to get those client ids and secrets, and add those callback urls.
Database Migrations
Now that Laravel Socialite is set, next we have to add a column in theusers
table. There are two ways to achieve this, first is to update our users table migration file directly and run migrate:refresh
artisan command:$table->json('settings')->nullable();
php artisan migrate:refresh
We are using json
type column here because a user might using the same email in their Facebook and Twitter for example. In this way we can associate those different accounts in one account in our system by checking their Facebook or Twitter id.
Second is to create a new migration file and run
migrate
artisan command:php artisan make:migration add_settings_to_users_table --table=users
$table->json('settings')->nullable();
php artisan migrate
This is usually useful when you don't want to delete your existing table in your database.Next, make sure to cast the
settings
column to array
in your User
model:
/**
* The attributes that should be casted to native types.
*
* @var array
*/
protected $casts = [
'settings' => 'array'
];
Routes
Next step is to add a routes toroutes/web.php
that will redirect to the provider that we are going to support (e.g., Facebook, Twitter, GitHub, etc.) and a callback url that will handle the data that is being returned by the third party provider.Route::get('auth/{driver}', ['as' => 'socialAuth', 'uses' => 'Auth\SocialController@redirectToProvider']);
Route::get('auth/{driver}/callback', ['as' => 'socialAuthCallback', 'uses' => 'Auth\SocialController@handleProviderCallback']);
Now, your button links might look like this:
<a href="/auth/facebook">Connect with Facebook</a>
<a href="/auth/twitter">Connect with Twitter</a>
<a href="/auth/google">Connect with Google</a>
The Controller
Next step is to create theAuth\SocialController
using the make:controller
artisan command.php artisan make:controller Auth/SocialController
The above command will generate a SocialController.php
file to app/Http/Controllers/Auth
directory.<?php
namespace App\Http\Controllers\Auth;
use App\Http\Requests;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Social\FacebookServiceProvider;
use GuzzleHttp\Exception\ClientException;
use Laravel\Socialite\Two\InvalidStateException;
use League\OAuth1\Client\Credentials\CredentialsException;
class SocialController extends Controller
{
protected $providers = [
'facebook' => FacebookServiceProvider::class,
];
/**
* Create a new controller instance
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Redirect the user to provider authentication page
*
* @param string $driver
* @return \Illuminate\Http\Response
*/
public function redirectToProvider($driver)
{
return (new $this->providers[$driver])->redirect();
}
/**
* Handle provider response
*
* @param string $driver
* @return \Illuminate\Http\Response
*/
public function handleProviderCallback($driver)
{
try {
return (new $this->providers[$driver])->handle();
} catch (InvalidStateException $e) {
return $this->redirectToProvider($driver);
} catch (ClientException $e) {
return $this->redirectToProvider($driver);
} catch (CredentialsException $e) {
return $this->redirectToProvider($driver);
}
}
}
Above is the complete code of SocialControler.php
. It's quite straight forward, isn't it? We just have to create a protected property $providers
array that holds the implementing classes for each third party provider that we're going to support. This approach somehow adheres the Single Responsibility Principle (SRP) in the SOLID object-oriented design.Next, we make sure the user accessing these entry points is a guest user, and in Laravel you just need a one line of code in the constructor for that, yes, you guessed it right, the built-in
middleware
method.Then in the
redirectToProvider
method we return the new instance of the class in the $providers
property and call a redirect
method from that class.Lastly in the
handleProviderCallback
method we try to execute the handle
method from the same class in the $providers
property and catch some exceptions and call the redirectToProvider
method in the SocialController
class, basically, if an exception occur the user will be redirected back to the provider (e.g., Facebook, Twitter, GitHub, etc.).
The Last Step
Now that our controller is ready, it's now time to create those classes in$providers
array property. But first let's create an abstract class that can be extended to those classes. Depending on your folder structures, I usually create a new folder for things like this. So let's create a Social
directory inside the app
directory and create a PHP file called AbstractServiceProvider.php
.<?php
namespace App\Social;
use App\User;
use Laravel\Socialite\Facades\Socialite;
abstract class AbstractServiceProvider
{
protected $provider;
/**
* Create a new SocialServiceProvider instance
*/
public function __construct()
{
$this->provider = Socialite::driver(
str_replace(
'serviceprovider', '', strtolower((new \ReflectionClass($this))->getShortName())
)
);
}
/**
* Logged in the user
*
* @param \App\User $user
* @return \Illuminate\Http\Response
*/
protected function login($user)
{
auth()->login($user);
return redirect()->intended('/');
}
/**
* Register the user
*
* @param array $user
* @return User $user
*/
protected function register(array $user)
{
$password = bcrypt(str_random(10));
$newUser = User::create(array_merge($user, ['password' => $password]));
return $newUser;
}
/**
* Redirect the user to provider authentication page
*
* @return \Illuminate\Http\Response
*/
public function redirect()
{
return $this->provider->redirect();
}
/**
* Handle data returned by the provider
*
* @return \Illuminate\Http\Response
*/
abstract public function handle();
}
In the constructor method we assign the socialite instance to the $provider
property by using the Socialite
facade and call the driver
method, which will accept a string.The
(new \ReflectionClass($this))->getShortName()
will return the name of the class that extends the AbstractServiceProvider
class.We also have an
abstract
handle
method that should be implemented by the child class or the class that will extend AbstractServiceProvider
class.After creating the abstract class, we can now start making those wrapper classes. In the same folder where the abstract class is located create a
FacebookServiceProvider.php
file.
<?php
namespace App\Social;
use App\User;
class FacebookServiceProvider extends AbstractServiceProvider
{
/**
* Handle Facebook response
*
* @return Illuminate\Http\Response
*/
public function handle()
{
$user = $this->provider->fields([
'first_name',
'last_name',
'email',
'gender',
'verified',
])->user();
$existingUser = User::where('settings->facebook_id', $user->id)->first();
if ($existingUser) {
$settings = $existingUser->settings;
if (! isset($settings['facebook_id'])) {
$settings['facebook_id'] = $user->id;
$existingUser->settings = $settings;
$existingUser->save();
}
return $this->login($existingUser);
}
$newUser = $this->register([
'first_name' => $user->user['first_name'],
'last_name' => $user->user['last_name'],
'email' => $user->email,
'gender' => ucfirst($user->user['gender']),
'settings' => [
'facebook_id' => $user->id,
]
]);
return $this->login($newUser);
}
}
What we're doing here is we check if the user is an existing user, if so we check their Facebook id if it's already in the settings
column, if not we will add it and log in them directly. Otherwise we will register them first before logging in.
Conclusion
That's all there is to it, the idea here is to make more easy adding functionality in the future with minimal changes in the files, in fact the only file you will probably change when adding new functionality is theAuth/SocialController.php
, like for example, if you wish to support Twitter or Google, all you have to do is add a key-value pair in the $providers
property array in SocialController
class. After that create a TwitterServiceProvider.php
file in app/Social
directory, extends
the AbstractServiceProvider
class and implement the handle
method.Hope you'll find this tutorial helpful. If you have any questions please don't hesitate to write it down in the comment section below and I'll try to answer it in the best of my ability. Thanks!
Post a Comment