DEV Community

Siddhu
Siddhu

Posted on • Updated on

API Authentication via Social Networks in Laravel 9 using Sanctum with Socialite

Laravel Sanctum with Socialite

Earlier I have written, How to use Laravel Sanctum and user authentication using API

Now, I'm going to add social logins using via API.

In this example, I'll show you how to integrate Laravel Sanctum authentication with social networks via Facebook, Google and GitHub.

Make sure, you have already installed Laravel Sanctum if not follow this article and then come here.

Laravel Socialite Configuration

STEP1: Install socialite package

composer require laravel/socialite

STEP2: Config socialite

You need to add all social config details to config\services.php file. As I shown below

'github' => [
    'client_id' => env('GITHUB_CLIENT_ID'),
    'client_secret' => env('GITHUB_CLIENT_SECRET'),
    'redirect' => 'GITHUB_REDIRECT_URI',
],
'facebook' => [
    'client_id' => env('FACEBOOK_CLIENT_ID'),
    'client_secret' => env('FACEBOOK_CLIENT_SECRET'),
    'redirect' => 'FACEBOOK_REDIRECT_URI',
],
'google' => [
    'client_id' => env('GOOGLE_CLIENT_ID'),
    'client_secret' => env('GOOGLE_CLIENT_SECRET'),
    'redirect' => 'GOOGLE_REDIRECT_URI',
],
Enter fullscreen mode Exit fullscreen mode

STEP3: Get config details from OAuth services providers

Now, You need to get CLIENT_ID and CLIENT_SECRET from respective social developer accounts, Here I provided the following links

GitHub https://github.com/settings/developers
Google https://console.developers.google.com/apis/dashboard
Facebook https://developers.facebook.com/apps/

It is easy to setup developer account in all social networks, where you can get the CLIENT_ID and CLIENT_SECRET keys. Finally, you need to set call-back URL. After successfully authentication it will send token to that URL.

STEP4: Set config details on your application

You have all CLIENT_ID and CLIENT_SECRET keys, Now add those keys into .env file

GITHUB_CLIENT_ID=XXXXXXXXX
GITHUB_CLIENT_SECRET=XXXXXXX
GITHUB_REDIRECT_URI=http://127.0.0.1:8000/api/login/github/callback
Enter fullscreen mode Exit fullscreen mode

Your GitHub account setting
Your GitHub account setting

Similarly add all other OAuth service providers

STEP5: Make password nullable

When you authenticate user with OAuth services, you will receive token not password. So, in our database the password field must be nullable.

Modify password field as nullable in users_table migration file.

public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password')->nullable();
            $table->rememberToken();
            $table->timestamps();
        });
    }
Enter fullscreen mode Exit fullscreen mode

STEP6: Create providers table

Now, you have to create another table for providers

php artisan make:migration create_providers_table

In the providers migration file, add this fields

    public function up()
    {
        Schema::create('providers', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('provider');
            $table->string('provider_id');
            $table->bigInteger('user_id')->unsigned();
            $table->string('avatar')->nullable();
            $table->timestamps();

            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
        });
    }
Enter fullscreen mode Exit fullscreen mode

STEP7: Create Provider table

Make Provider model

php artisan make:model Provider

open Provider model

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Provider extends Model
{
    use HasFactory;

    protected $fillable = ['provider','provider_id','user_id','avatar'];
    protected $hidden = ['created_at','updated_at'];
}
Enter fullscreen mode Exit fullscreen mode

STEP8: Add provider relation to user table

Here I have created Providers function on user model

<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;


class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var string[]
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];


    public function providers()
    {
        return $this->hasMany(Provider::class,'user_id','id');
    }
}

Enter fullscreen mode Exit fullscreen mode

STEP9: Add the Routes

Open routes/api.php file add route URL's

Route::get('/login/{provider}', [AuthController::class,'redirectToProvider']);
Route::get('/login/{provider}/callback', [AuthController::class,'handleProviderCallback']);
Enter fullscreen mode Exit fullscreen mode

Add above functions on your controller


<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Hash;
use GuzzleHttp\Exception\ClientException;
use Illuminate\Http\JsonResponse;
use Laravel\Socialite\Facades\Socialite;
use App\Models\User;

class Authcontroller extends Controller
{


    /**
     * Redirect the user to the Provider authentication page.
     *
     * @param $provider
     * @return JsonResponse
     */
    public function redirectToProvider($provider)
    {
        $validated = $this->validateProvider($provider);
        if (!is_null($validated)) {
            return $validated;
        }

        return Socialite::driver($provider)->stateless()->redirect();
    }

    /**
     * Obtain the user information from Provider.
     *
     * @param $provider
     * @return JsonResponse
     */
    public function handleProviderCallback($provider)
    {
        $validated = $this->validateProvider($provider);
        if (!is_null($validated)) {
            return $validated;
        }
        try {
            $user = Socialite::driver($provider)->stateless()->user();
        } catch (ClientException $exception) {
            return response()->json(['error' => 'Invalid credentials provided.'], 422);
        }

        $userCreated = User::firstOrCreate(
            [
                'email' => $user->getEmail()
            ],
            [
                'email_verified_at' => now(),
                'name' => $user->getName(),
                'status' => true,
            ]
        );
        $userCreated->providers()->updateOrCreate(
            [
                'provider' => $provider,
                'provider_id' => $user->getId(),
            ],
            [
                'avatar' => $user->getAvatar()
            ]
        );
        $token = $userCreated->createToken('token-name')->plainTextToken;

        return response()->json($userCreated, 200, ['Access-Token' => $token]);
    }

    /**
     * @param $provider
     * @return JsonResponse
     */
    protected function validateProvider($provider)
    {
        if (!in_array($provider, ['facebook', 'github', 'google'])) {
            return response()->json(['error' => 'Please login using facebook, github or google'], 422);
        }
    }

}

Enter fullscreen mode Exit fullscreen mode

STEP10: Final step, Call the OAuth services URL

In my case I setup everything in my local, open

http://127.0.0.1:8000/api/login/github

github login

After successfully login you will receive

github response

Sometimes routes api url will throw errors, try to use this command php artisan route:clear

Please feel free to ask me anything on this topic!

Top comments (22)

Collapse
 
zaidshahid profile image
zaid-shahid

How can we test it using postman, please answer.

Collapse
 
siddhartha profile image
Siddhu • Edited

I'm not sure, How to test using Postman.

You need at least one sample html page with some google js code

Collapse
 
siddhartha profile image
Siddhu

I'll send you git repo, you can download

Collapse
 
codeperl profile image
mohammad ahsrafuddin ferdousi

Hi Sidhhu,

It's a nice article and thanks for this. I will use this to my new project. As others, I am also curious about the test code and so, is it possible to share the github repo to me too?

Thanks in advance!

Thread Thread
 
siddhartha profile image
Siddhu

Sure, I'll share

Thread Thread
 
siddhartha profile image
Siddhu
Thread Thread
 
codeperl profile image
mohammad ahsrafuddin ferdousi • Edited

Thank you! I will surely help me to look forward!

Update: I was checking the codebase you share. It seems that mistakenly there are no test code for sanctum + socialite?

Thread Thread
 
siddhartha profile image
Siddhu

You are right, I lost some where.

Anyway I'm doing with with new instance with laravel 9.

I'll update the article

Thread Thread
 
codeperl profile image
mohammad ahsrafuddin ferdousi

That's so helpful!

However, I am facing an issue. If somehow you may suggest me any article in such case, I'll be glad Sidhhu.

I am deploying my nuxt app using nginx as reverse proxy server in one docker container.

I deployed my laravel api in another docker container and using nginx as webserver.

Now, even if everything done quite okay "to me" by fixing cors, when calling api from nuxt app it shows this error in chrome:

Access to XMLHttpRequest at 'https://api.laraecom.com/api/v1/users/auth/social/login' from origin 'https://laraecom.com:4430' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header contains multiple values 'https://laraecom.com:4430, *', but only one is allowed.

If you can suggest anything that will be a great help! I am stuck here more than a week!

Thanks in advance!

Thread Thread
 
siddhartha profile image
Siddhu

You have to enable cors

This article will help you dev.to/keikesu0122/a-simple-way-to...

Collapse
 
the_olusolar profile image
Solar

Hi, please can I get the repo too?
I really need to implement this via API instead of browser

Collapse
 
melenchenko profile image
melenchenko • Edited

In this example you show generated access token in browser to end-user via headers. How can I transfer this token to frontend application for further authentications of API requests to my server?

Collapse
 
medilies profile image
medilies

I demonstrated that here dev.to/medilies/laravel-spa-oauth-...

Collapse
 
marienmupenda profile image
Marien Mupenda

Thank for this article.

I can see the call back returns a JSON response, will the response be displayed to the end user on it's browser ?

I am wondering if we should display the response or redirect or something mon user friendly.

Collapse
 
hernanarica profile image
Hernán Arica

in this way I can not authenticate with a frontend made in react

Collapse
 
medilies profile image
medilies
Collapse
 
hanifullahjamalzai profile image
HanifullahJamalzai

I would love to implement Socialite for blog posts not for admin dashboard, I have lost my way how to implement.

Thanks in advance

Collapse
 
siddhartha profile image
Siddhu

You can follow above mentioned steps, if you stuck or doubt anywhere. Pls comment here.

I'll try to provide solutions to my best

Collapse
 
bekzhan99 profile image
Bekzhan

i use two domains one api.beta.website second beta.website. when I make a request through the beta on the socialite it gives errors

Collapse
 
siddhartha profile image
Siddhu

what error that you are facing?

Collapse
 
bekzhan99 profile image
Bekzhan

I make a request through the beta domain to api.beta and there is a request on Google and there is a cors error

Thread Thread
 
siddhartha profile image
Siddhu

Understood, this will help you out. medium.com/@martinsOnuoha/fix-cors...