Social login API with Laravel + Passport

Image for post
Image for post
Photo by inlytics on Unsplash

This article is about how we can integrate social login API in our laravel project using passport.

Assumption

I am going to assume that you have already integrated passport in your project. If you haven’t, you can visit the official documentation.

Requirements

  1. Laravel passport installed and configured.

Packages

  1. Socialite
  2. laravel-passport-socialite

We need Socialite to enable social authentication functionality in our project. Laravel provides a simple, convenient way to authenticate with OAuth providers using Laravel Socialite. Socialite currently supports authentication with Facebook, Twitter, LinkedIn, Google, GitHub, GitLab, and Bitbucket.

Unfortunately, Passport doesn’t provide a grant for social login. So, we also need to install laravel-passport-socialite plugin to provide the missing social grant. It is a very small package. It acts as a bridge between laravel’s native packages i.e. (passport and socialite).

Steps

  1. Install socialite
composer require laravel/socialite

2. Configure Socialite

You will need to 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 facebook, twitter, linkedin, google, github, gitlab or bitbucket, depending on the providers your application requires.

For example: If I wish to use Facebook and Google authentication in my project then I need to place the following in config/services.php :

'facebook' => [
'client_id' => env('FACEBOOK_CLIENT_ID'),
'client_secret' => env('FACEBOOK_CLIENT_SECRET'),
'redirect' => env('FACEBOOK_URL'),
],

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

Also in .env file,

FACEBOOK_CLIENT_ID=
FACEBOOK_CLIENT_SECRET=
FACEBOOK_URL=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_URL=

You need to fill in the client_id and client_secret value provided by the respective provider.

3. Update users table

Now, we need to add the following two columns in the users table to store the provider name and the provider id provided by the respective provider.

$table->string('provider')->nullable();
$table->string('provider_id')->nullable();

provider column will store provider name like google or facebook. provider_id will store a id provided by the respective provider.

Then migrate the changes.

php artisan migrate

4. Update User model

We need to make provider and provider_id columns fillable in our users table by adding those the fillable attribute.

protected $fillable = [
'name',
'email',
'password',
'provider', 'provider_id'
];

5. Create AccessTokenController.php

Now we need to create AccessTokenController to overwrite the issueToken function provided by Passport.

<?php

namespace App\Http\Controllers;

use GuzzleHttp\Exception\ClientException;
use Illuminate\Http\JsonResponse;
use Laravel\Passport\Http\Controllers\AccessTokenController as PassportAccessTokenController;
use League\OAuth2\Server\Exception\OAuthServerException;
use Psr\Http\Message\ServerRequestInterface;
use Nyholm\Psr7\Response as Psr7Response;

class AccessTokenController extends PassportAccessTokenController
{
public function issueToken(ServerRequestInterface $request)
{
try {
$response = $this->getResponse($request);
$token = json_decode($response->getContent(), true)['access_token'];
return response()->json([
'success' => true,
'message'=>'User token generated',
'token' => $token
]);
} catch (ClientException $exception) {
$error = json_decode($exception->getResponse()->getBody());

throw OAuthServerException::invalidRequest('access_token', object_get($error, 'error.message'));
}
}

public function getResponse($request){
return $this->convertResponse(
$this->server->respondToAccessTokenRequest($request, new Psr7Response)
);
}
}

6. Binding AccessTokenController

We need to bind the passport’s AccessTokenController with our controller’s AccessTokenController.

Go to App\Providers\AppServiceProvider.php and place the following in the binding attribute.

public $bindings = [

\Laravel\Passport\Http\Controllers\AccessTokenController::class => \App\Http\Controllers\AccessTokenController::class
];

At this point, we need to install laravel-passport-socialite.

7. Install laravel-passport-socialite plugin

composer require schedula/laravel-passport-socialite

8. Configure laravel-passport-socialite plugin

When the composer installs this package successfully, register the Schedula\Laravel\PassportSocialite\PassportSocialiteServiceProvider::class in your config/app.php configuration file.

'providers' => [
// Other service providers...
Schedula\Laravel\PassportSocialite\PassportSocialiteServiceProvider::class,
],

In step 9 and 10 we configure a repository to keep our logic for social login. But It can be done differently.

9. Create User repository

We can create a user repository to store the logic to create and update our social user.

create App\Repositories\User\UserReporitory.php

<?php

namespace App\Repositories\User;

use App\Models\User;
use Laravel\Socialite\Two\User as ProviderUser;

class UserRepository implements UserRepositoryInterface
{

public function socialLogin(ProviderUser $providerUser, string $provider)
{
try{
$user = User::where('provider_id', $providerUser->getId())->first();
if (!$user) {
if($providerUser->getEmail()){
$user = User::where('email', $providerUser->getEmail())->first();
if($user){
$this->updateSocialUser($user, $providerUser, $provider);
}
else{
$user = $this->createSocialUser($providerUser, $provider);
$user->markEmailAsVerified();
}
}else{
$user = $this->createSocialUser($providerUser, $provider);
}
}
return $user;
}catch(\Exception $e){
return $e;
}
}

private function createSocialUser($social_user, $provider)
{
return User::create([
'name' => $social_user->getName(),
'email' => $social_user->getEmail(),
'provider_id' => $social_user->getId(),
'provider' => $provider,
]);
}

private function updateSocialUser($user, $social_user, $provider)
{
$user->update([
'name' => $social_user->getName(),
'email' => $social_user->getEmail(),
'provider_id' => $social_user->getId(),
'provider' => $provider,
]);
}

}

Also, create App\Repositories\User\UserReporitoryInterface.php

<?php
namespace App\Repositories\User;

use Laravel\Socialite\Two\User as ProviderUser;

interface UserRepositoryInterface
{
public function socialLogin(ProviderUser $providerUser, string $provider);
}

10. Binding repository and interface

Create a new service provider App\Providers\RepositoryServiceProvider.php .

Now bind App\Repositories\User\UserReporitoryInterface.php with App\Repositories\User\UserReporitory.php in App\Providers\RepositoryServiceProvider.php .

<?php

namespace App\Providers;

use App\Repositories\User\UserRepository;
use App\Repositories\User\UserRepositoryInterface;
use Illuminate\Support\ServiceProvider;

class RepositoryServiceProvider extends ServiceProvider
{
/**
* Register services.
*
*
@return void
*/
public function register()
{
$this->app->bind(UserRepositoryInterface::class, UserRepository::class);
}

/**
* Bootstrap services.
*
*
@return void
*/
public function boot()
{
//
}
}

Now, include RepositoryServieProvider in config/app.php .

'providers' => [
// Other service providers...
App\Providers\RepositoryServiceProvider::class,
],

11. Create Resolver

Now we have to create a resolver file in App\Services\Resolver\SocialUserResolver.php .

<?php

namespace App\Services\Resolver;

use App\Repositories\User\UserRepository;
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)->stateless()->userFromToken($accessToken);
} catch (Exception $exception) {
}

if ($providerUser) {
$userRepository = new UserRepository();
return $userRepository->socialLogin($providerUser, $provider);
}

return null;
}
}

Here we use the user repository. If you have done it differently, you can change.

12. Binding the resolver

Go to App\Providers\AppServiceProvider.php and place the following inside the binding attribute.

\Coderello\SocialGrant\Resolvers\SocialUserResolverInterfaceSocialUserResolverInterface::class => \App\Services\Resolver\SocialUserResolverSocialUserResolver::class,

That’s it in our project. Now we need to create a new request in Postman.

13. Create social login request

Create a post request <project_url>/oauth/token with following parameters. This oauth/token is provided by passport. You don't need to create a new route for this.

Image for post
Image for post

All the fields are required.

grant_type: always “social” for social login.

client_id: id from oauth_clients database table.

client_secret: secret column value from oauth_clients table

provider: provider name like facebook or google.

access_token: access token provided by the respective provider.

You can see this article to know how to get a google access token for your email.

After filling all the fields, send the request and you will get a token in the response. And you can use the token to make other authorized requests.

Hope it helps!

Written by

Web developer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store