Introduction

Laravel makes API authentication a breeze using Laravel Passport, which provides a full OAuth2 server implementation for your Laravel application in a matter of minutes.

But what about API authentication via social networks? Laravel Passport doesn’t provide such ability by default. So we are going to implement that in this article. Let’s go!

Step 1: Install and configure Laravel Passport

This article assumes you are already familiar with Laravel Passport (can install and configure it on your own) so this process won’t be described. If not, then it’s recommended to familiarize yourself with API Authentication (Passport) section of Laravel documentation and go back to this tutorial.

Step 2: Install and configure Laravel Socialite

Install Laravel Socialite with:

composer require laravel/socialite

Then add credentials for the OAuth services your application utilizes. These credentials should be placed in your config/services.php configuration file, and should use the key equals to provider name (e.g., facebook, google, github etc.)

For example:

'google' => [
    'client_id' => env('GOOGLE_CLIENT_ID'),
    'client_secret' => env('GOOGLE_CLIENT_SECRET'),
    'redirect' => env('GOOGLE_REDIRECT_URL'),
],

We will use Socialite just for retrieving user details from an access token so we can fill client_id, client_secret, redirect with empty strings (not NULL) because they won’t be used in our flow.

If you want to use a provider that is not provided in Socialite by default take a look on Socialite Providers.

Step 3: Implement managing of social accounts that are linked to users

Create LinkedSocialAccount model with according migration:

php artisan make:model Models\\LinkedSocialAccount -m
<?php
namespace App;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
class LinkedSocialAccount extends Model
{
    protected $fillable = [
        'provider_name',
        'provider_id',
    ];
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateLinkedSocialAccountsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('linked_social_accounts', function (Blueprint $table) {
            $table->increments('id');
            $table->string('provider_id');
            $table->string('provider_name');
            $table->unsignedInteger('user_id');
            $table->timestamps();
            $table->foreign('user_id')->references('id')->on('users');
        });
    }
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('linked_social_accounts');
    }
}

Add linkedSocialAccounts relation for User model.

<?php
namespace App;
use App\Models\LinkedSocialAccount;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable
{
    use HasApiTokens;
    protected $fillable = [
        'name',
        'email',
        'password',
    ];
    protected $hidden = [
        'password',
        'remember_token',
    ];
    public function linkedSocialAccounts()
    {
        return $this->hasMany(LinkedSocialAccount::class);
    }
}

Make password, email fields nullable in users table by creating a migration:

php artisan make:migration make_password_and_email_fields_nullable_in_users_table --table=users
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class MakePasswordAndEmailFieldsNullableInUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('email')->nullable()->change();
            $table->string('password')->nullable()->change();
        });
    }
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('email')->change();
            $table->string('password')->change();
        });
    }
}

Run all migrations:

php artisan migrate

Create an SocialAccountsService that will be responsible for finding/creating of User instance by provider credentials.

<?php
namespace App\Services;
use App\Models\User;
use App\Models\LinkedSocialAccount;
use Laravel\Socialite\Two\User as ProviderUser;
class SocialAccountsService
{
    /**
     * Find or create user instance by provider user instance and provider name.
     * 
     * @param ProviderUser $providerUser
     * @param string $provider
     * 
     * @return User
     */
    public function findOrCreate(ProviderUser $providerUser, string $provider): User
    {
        $linkedSocialAccount = \App\LinkedSocialAccount::where('provider_name', $provider)
            ->where('provider_id', $providerUser->getId())
            ->first();
        if ($linkedSocialAccount) {
            return $linkedSocialAccount->user;
        } else {
            $user = null;
            if ($email = $providerUser->getEmail()) {
                $user = User::where('email', $email)->first();
            }
            if (! $user) {
                $user = User::create([
                    'first_name' => $providerUser->getName(),
                    'last_name' => $providerUser->getName(),
                    'email' => $providerUser->getEmail(),
                ]);
            }
            $user->linkedSocialAccounts()->create([
                'provider_id' => $providerUser->getId(),
                'provider_name' => $provider,
            ]);
            return $user;
        }
    }
}

Step 4: Install and configure Laravel Passport Social Grant

In order to enable social grant we will use Laravel Passport Social Grant. Install it with:

composer install coderello/laravel-passport-social-grant 

To make it work create SocialUserResolver that implements SocialUserResolverInterface where we will:

  • retrieve user details from an access token (with usage of Laravel Socialite);
  • return null if Socialite has thrown an exception;
  • otherwise find/create the User instance corresponding to user details retrieved above (with usage of SocialAccountsService);
  • return found/created User instance.
<?php
namespace App\Services;
use Exception;
use Coderello\SocialGrant\Resolvers\SocialUserResolverInterface;
use Illuminate\Contracts\Auth\Authenticatable;
use Laravel\Socialite\Facades\Socialite;
class SocialUserResolver implements SocialUserResolverInterface
{
    /**
     * Resolve user by provider credentials.
     *
     * @param string $provider
     * @param string $accessToken
     *
     * @return Authenticatable|null
     */
    public function resolveUserByProviderCredentials(string $provider, string $accessToken): ?Authenticatable
    {
        $providerUser = null;
        
        try {
            $providerUser = Socialite::driver($provider)->userFromToken($accessToken);
        } catch (Exception $exception) {}
        
        if ($providerUser) {
            return (new SocialAccountsService())->findOrCreate($providerUser, $provider);
        }
        return null;
    }
}

Bind SocialUserResolverInterface to our implementation. You can do it by adding the appropriate key-value pair to $bindings property in AppServiceProvider:

<?php
namespace App\Providers;
use App\Services\SocialUserResolver;
use Coderello\SocialGrant\Resolvers\SocialUserResolverInterface;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
    /**
     * All of the container bindings that should be registered.
     *
     * @var array
     */
    public $bindings = [
        SocialUserResolverInterface::class => SocialUserResolver::class,
    ];
}

Step 5: Ensure that all works perfectly

PostMan

As you can see it works like a charm!