Laravel

Laravel 5.6 Socialite Tutorial

February 15, 2018

author:

Laravel 5.6 Socialite Tutorial

Laravel is very famous for its ready-to-use modules. One of them is Authentication which was introduced in Laravel 5.2. It generates all the required controllers, routes and views with a single artisan command viz. php artisan make:auth.

While this works best to set up a conventional login system in the application, the web is moving towards social logins, i.e., Facebook, Google, etc. Today, we will try and integrate this model into a Laravel application. In this tutorial, we will configure preliminary setup Facebook and Google OAuth.

# Laravel Socialite Project Setup

First off, let us craft a fresh Laravel project, you can also use your other Laravel projects.

laravel new socialiteApp

Also, generate .env file and app key, if it isn’t already done for you:

cp .env.example .env
php artisan key:generate

# Socialite Database Setup

As we have learned earlier, Laravel stores database settings in environment config file called .env. I’m using MySQL database, setup your relevant database credentials:

# .env

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=social
DB_USERNAME=root
DB_PASSWORD=

# Socialite Model and Migrations

Laravel provides users and password resets migration by default. It also provides a working User model. These are used with Laravel Auth setup. We can rewrite them to suit our requirements or create new migration table and model to improve code readability in the future. Let’s choose the second option and create fresh migration and model first:

php artisan make:model SocialAccount -m
Add migration & model in Laravel 5.6

As seen, the above command created both model and migration at once.

# Migration Setup

Add following fields in the create_social_accounts_table so as to store relevant user data:

# database/migrations/..._create_social_accounts_table.php
...
public function up()
{
    Schema::create('social_accounts', function (Blueprint $table) {
        $table->increments('id');
        $table->integer('user_id')->unsigned();
        $table->string('provider_user_id');
        $table->string('provider');
        $table->timestamps();
        $table->softDeletes();
    });

    Schema::table('social_accounts', function (Blueprint $table) {
        $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
    });
}
...

Here provider_user_id will store provider (google/facebook) specific id and provider stores provider name. We are also using soft deletes.

Also, make the password nullable in the table as a user who uses social login won’t pass a password. This can be a compromise for the application, but we can validate the password input from the form.

# database/migrations/..._create_users_accounts_table.php
...
public function up()
{
    Schema::create('users', function (Blueprint $table) {
        ...
        $table->string('password')->nullable();
    });
}
...

# Relationships

Situation:
A user usually uses same email id for his emails and social accounts. In that case, if we keep creating new records for each new conventional login and social login separately, we may end collecting duplicate emails. On the other hand, if we configure email to be unique, problems will arise when a user uses same email id for both Google and Facebook and will disbar the access.

The Solution:
To save the application from all these hassles, we already created a separate table for social accounts so now let us map both User and SocialLogin model.

Each user can have a social accounts. Thus we will setup One-to-One relationship between User and SocialAccount.

# app/User.php
...
class User extends Authenticatable
{
    ...
    public function profile()
    {
        return $this->hasOne('App\SocialProfile');
    }
}

Let us also create a belongsTo relationship with User.

# app/SocialAccount.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class SocialAccount extends Model
{
    use SoftDeletes;
    protected $dates = ['deleted_at'];
    protected $fillable = ['user_id', 'provider_user_id', 'provider'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

We just mapped User model here and also integrated soft deletes and $fillable. Everything looks good, let us now migrate the database tables:

php artisan migrate
Migration command in Laravel 5.6

Did you face the following error while running migration? (If not, move ahead)
Migration Run Error in Laravel 5.6

Here’s a small solution to it. Just add these two lines of code in app/Providers/AppServiceProvider.php

# app/Providers/AppServiceProvider.php
...
use Illuminate\Support\Facades\Schema;

class AppServiceProvider extends ServiceProvider
{
    ...
    public function boot()
    {
        Schema::defaultStringLength(191);
    }
    ...
}

# Socialite Package Setup

Laravel has a ready-to-use package to configure social logins in the application named Socialite. It was initially a third-party library, but with the newer version of Larvel, it was merged with it. We need to install it anyway:

composer require laravel/socialite

For version 5.5+ in Laravel, the Service Provider and Alias are mapped automatically so we don’t need to add them manually.

# Routes for Social Login

We will create three endpoints in the application:

  • Main Endpoint for login buttons with Google and Facebook
  • Social endpoint to redirect to respective login screens
  • Callback endpoint that the respective website must redirect on successful login

And here they are:

# routes/web.php
<?php
...
Route::get('/', function () {
    return view('welcome');
});
Route::get('/login', function () {
    return view('socialLogin');
});
Route::get('/auth/social/{social}', 'SocialLoginController@redirectToSocial');
Route::get('/auth/{social}/callback', 'SocialLoginController@handleSocialCallback');

# Blade for Social Login

In routes, we are setting an endpoint for login buttons, so let us first add them. Create a fresh blade file socialLogin.blade.php in resources/views namespace.

# resources/views/socialLogin.blade.php
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Social Login In Laravel 5.6</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Fonts -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<!-- Styles -->
<style>
body {
background-color: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
}
.social-login {
display: flex;
flex-direction: column;
background-color: white;
justify-content: center;
width: 400px;
height: 220px;
max-width: 50vw;
padding: 15px 35px;
border-radius: 5px;
box-shadow: 0 10px 40px 0 rgba(0, 0, 0, 0.1);
}
.social-login h2 {
margin: 20px 0 20px;
padding: 0;
text-transform: uppercase;
color: #000;
position: relative;
text-align: center;
}
.social-button--facebook {
list-style: none;
background-color: transparent;
border-width: 2px;
border-style: solid;
border-color: #3A5998;
color: #3A5998;
cursor: pointer;
font-size: 14px;
padding: 15px;
display: flex;
border-radius: 5px;
align-items: center;
text-decoration: none;
text-transform: uppercase;
transition: background-color 250ms ease-out, color 250ms ease-out;
font-weight: 700;
position: relative;
margin: 10px 0;
z-index: 0;
will-change: background-color, color;
user-select: none;
-webkit-font-smoothing: antialiased;
}
.social-button--facebook:focus, .social-button--facebook:hover {
background-color: #bac8e4;
}
.social-icon--facebook {
color: white;
font-size: 18px;
}
.social-icon--facebook:after {
content: "";
background-color: #3A5998;
position: absolute;
width: 50px;
height: 100%;
top: 0;
left: 0;
z-index: -1;
}
.social-button--google {
list-style: none;
background-color: transparent;
border-width: 2px;
border-style: solid;
border-color: #DB4437;
color: #DB4437;
cursor: pointer;
font-size: 14px;
padding: 15px;
display: flex;
border-radius: 5px;
align-items: center;
text-decoration: none;
text-transform: uppercase;
transition: background-color 250ms ease-out, color 250ms ease-out;
font-weight: 700;
position: relative;
margin: 10px 0;
z-index: 0;
will-change: background-color, color;
user-select: none;
-webkit-font-smoothing: antialiased;
}
.social-button--google:focus, .social-button--google:hover {
background-color: #fae6e4;
}
.social-icon--google {
color: white;
font-size: 18px;
}
.social-icon--google:after {
content: "";
background-color: #DB4437;
position: absolute;
width: 50px;
height: 100%;
top: 0;
left: 0;
z-index: -1;
}
.social-text {
width: 100%;
text-align: center;
}
</style>
</head>
<body>  
<div class="social-login">
<h2>Social Login in Laravel 5.6</h2>
<a href="{{ url('/auth/social/google') }}" class="social-button--google">
<i class="social-icon--google fa fa-google"></i>
<span class="social-text">
Sign in with google
</span>
</a>
<a href="{{ url('/auth/social/facebook') }}" class="social-button--facebook">
<i class="social-icon--facebook fa fa-facebook"></i>
<span class="social-text">
Sign in with facebook
</span>
</a>
</div>  
</body>
</html>

We can test it by running:

http://localhost:8000/login
Laravel 5.6 social login screen

# Controller for Social Login

Let us first create a new controller to handle social logins in Laravel:

php artisan make:controller SocialLoginController

Since we want to setup two social logins (Google and Facebook), and as the application scales, the other providers may also be needed, let us setup, dynamic Socialite.

public function redirectToSocial()
{
//static
Socialite::driver('facebook')->redirect();
Socialite::driver('google')->redirect();
Socialite::driver('twitter')->redirect();
Socialite::driver('github')->redirect();
}
# app/Http/Controllers/SocialLoginController.php
...
class SocialLoginController extends Controller
{
//dynamic
public function redirectToSocial($social)
{
return Socialite::with($social)->redirect();
}
}

The social login provider sends back data to the application on successful login or error in case of unsuccessful login. Here’s how we can handle that:

# app/Http/Controllers/SocialLoginController.php
...
class SocialLoginController extends Controller
{
...
function handleSocialCallback(SocialAccountService $service, $social)
{        
try {
$user = $service->setOrGetUser(Socialite::driver($social));
auth()->login($user);
return redirect('/');
} catch (\Exception $e) {
return $e;
}
}
}

In the above code snippet, we are using SocialAccountService (which we will set up in a moment!) and adding new user to the database and also authenticating the user to browse the application.

Thus, here’s how SocialLoginController looks now:

# app/Http/Controllers/SocialLoginController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Reqauest;
use Socialite;
use App\SocialAccountService;
class SocialLoginController extends Controller
{
public function redirectToSocial($social)
{
return Socialite::with($social)->redirect();
}
function handleSocialCallback(SocialAccountService $service, $social)
{        
try {
$user = $service->setOrGetUser(Socialite::driver($social));
auth()->login($user);
return redirect('/');
} catch (\Exception $e) {
return $e;
}
}
}

# Insert Records in Database

As discussed earlier, we would want to keep track of user logging in with their social accounts. We already have a working migration table and model for it. So now let us work around creating records in the database. To keep this logic independent, we intentionally have not added this in the controller we created in the previous step.

We will create a different service that will take provider response as a parameter, check if the user email and provider details already exist in the database, add if not and finally return back the user details to the controller. Here’s the whole scenario in action:

# app/SocialAccountService.php
<?php
namespace App;
use Laravel\Socialite\Contracts\Provider;
class SocialAccountService
{
public function setOrGetUser(Provider $provider)
{
$providerUser = $provider->user();
$providerName = class_basename($provider);
$account = SocialAccount::whereProvider($providerName)
->whereProviderUserId($providerUser->getId())
->first();
if ($account) {
return $account->user;
} else {
$account = new SocialAccount([
'provider_user_id' => $providerUser->getId(),
'provider' => $providerName
]);
$user = User::whereEmail($providerUser->getEmail())->first();
if (!$user) {
$user = User::create([
'email' => $providerUser->getEmail(),
'name' => $providerUser->getName(),
'username' => strtolower(preg_replace('/\s+/', '_', $providerUser->name) . mt_rand(10, 100))
]);
}
$account->user()->associate($user);
$account->save();
return $user;
}
}
}

And that concludes the overall setup to work with the socialite in Laravel. It may be difficult for some developers to digest all the information at once. So let’s take a break and continue configuring Google and Facebook login in the next post.

Conclusion:
In this post, we started setting up the Laravel application from scratch to configure social logins like Google and Facebook. In the next session, we will work with Google and Facebook’s developer console to get API keys and integrate them into this application. Don’t miss on all the fun there.

Questions & Comments:

Thanks for reading. Drop your suggestions and questions in the comment section below.

Leave a comment

Your email address will not be published. Required fields are marked *