API authentication via social networks for your Laravel application
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 require 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 ofSocialAccountsService
); - 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
As you can see it works like a charm!