Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

computerfr33k's avatar

Using socialite/social login with Laravel passport

I am developing an API with Laravel and want to support users being able to login with Facebook, google, etc. I'm wondering if anyone knows if it is possible to support social login while authenticating users with Laravel passport or how I can even support this kind of auth as an API provider.

Please let me know if I should clarify anything else.

0 likes
18 replies
WebKenth's avatar

You can link your Users accounts with their social one, if that's what you want?

So like you have a User who can both login with Facebook/Google+ and their account info on your site

But why they would do this through an API provider i'm not sure

1 like
computerfr33k's avatar

I don't think I explained it quite well. I have an API that I wrote using Laravel and am currently using Laravel Passport for API authentication, but want to know if it possible to have the mobile app have the user authenticate using their social account (FB, Google, etc) but then also be authenticated to make API calls. I understand how to do this using socialite with a webapp part of laravel, just unsure how to support social logins for the API portion of laravel.

2 likes
joejordanbrown's avatar

@computerfr33k

On the Socialite callback you will need to check if the user is already registered comparing the email address returning by socialite and comparing the id that's returned by the socialite driver. If the social account is linked to an account you can use Passport API to generate access_token and return back to your app for the app to use for future requests.

If it's a new user you can generate a user using the information returned from Socialite and return back to the app to finish of registering if more details are required to be filled in for the user.

You can also allow users to link an existing account to multiple socialite drivers.

$user_by_email = User::where('email',  Socialite::driver('facebook')->stateless()->user()->email )->first();
$user_by_id = OAuthUser::where('oauth_id', Socialite::driver('facebook')->stateless()->user()->id )->first();

You should also create the following tables and models:

table: oauth_users

| id (int) | user_id (int) | oauth_id (varchar) | access_token (varchar) | refresh_token (varchar) | oauth_driver_id (int) | created_at | updated_at |

<?php namespace App;

use Illuminate\Database\Eloquent\Model;

class OAuthUser extends Model
{


    protected $table = 'oauth_users';

    protected $primaryKey = 'id';

    public $timestamps = true;

    protected $fillable = [
    ];

    protected $hidden = [
    ];

    protected $guarded = [
        'id',
        'user_id',
        'oauth_id',
        'access_token',
        'refresh_token',
        'oauth_driver_id'
    ];


    public function oauthDriver() {
        return $this->hasOne('App\OAuthDriver', 'oauth_driver_id', 'id');
    }
}

table: oauth_drivers

| id (int) | name (varchar) | active (int) | created_at | updated_at |

| 1 | facebook | 1 | 2016-10-18 00:57:32 | 2016-10-18 00:57:32 |

| 2 | github | 1 | 2016-10-18 00:57:32 | 2016-10-18 00:57:32 |

| 3 | linkedin | 0 | 2016-10-18 00:57:32 | 2016-10-18 00:57:32 |

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class OAuthDriver extends Model
{
   
    protected $table = 'oauth_drivers';

    protected $primaryKey = 'id';

    public $timestamps = true;

    protected $fillable = [
        'name',
        'active'
    ];

    protected $hidden = [
    ];

    protected $guarded = [
        'id'
    ];

}

I hope I've explained that clearly. If you need examples of how the controller should look let me know.

5 likes
bnsteel's avatar

How fortuitous , I also am interested in doing this! I am going through the tutorials about setting up Socialite and Passport (again) . I have a ridiculous number of questions that I am in the process of working out how to answer myself, but I'll put them here anyway.

Registration Options:

  1. Become a Client and use Passport Oauth and do handshake with me to get token
  2. Allow Oauth from another provider and do handshake with them. If they have code, give them token

If then have my token, let them use the API. (I am using the Passport style guard middleware)

I was planning on using the "findByUsernameOrCreate" method described in the Socialite video, essentially the $user_by_email method of tracking folks.

I don't understand what I would do with the OAuthUser table information.

Do I need to remember the access tokens so I can re-issue it, or do I need to verify myself that it is the correct token? Help me Obi-Wan!

2 likes
joejordanbrown's avatar

@bnsteel

The reason I'm not suggesting to use the findByUsernameOrCreate method they create is because that checks 3 fields username, email and avatar. That's fine if you are going to be only using one driver like the example. But if you are using many drivers and want the user to be able to use any of them drivers linked to the user account to login then checking them 3 fields would not work.

Pros:

- Multiple linked drivers.

- Driver changing email address still works.

- You can use the refresh token to call API and update information if needed.

By having oauth_users table you can store the returned unique UID for that driver and also link to the driver_id so a user can have multiple OAuth drivers/providers linked to their account.

I added the oauth_drivers active field because then you could easily in your code only show/allow drivers that are active.

A simplified example:

$user_by_email = User::where('email',  Socialite::driver('facebook')->stateless()->user()->email )->first();
$user_by_id = OAuthUser::where('oauth_id', Socialite::driver('facebook')->stateless()->user()->id )->first();

if ($user_by_id) {
    $user = User::find($user_by_id->user_id);
    // return passport access_token
}
elseif ($user_by_email) {
    return Response()->json(['error' => 324, 'message' => 'You already have an account please signin using login credentials and link this social account to use in future.']);
}
else {
    // Create user
    // if successful then return passport access_token
}
computerfr33k's avatar

Where can I find more documentation on socialite, because they don't mention anything about the stateless method in the github readme and there is no link to more detailed documentation. Thanks!

computerfr33k's avatar

Here's what I've come up with for the routes, not sure if this is a good way of doing it.

Route::get('{provider}/authorize', function ($provider) {
    return Socialite::driver($provider)->stateless()->redirect();
});
Route::get('{provider}/login', function ($provider) {

    $socialite = Socialite::driver($provider)->stateless()->user();

    $user_by_email = User::where('email', $socialite->email)->first();
    $user_by_id = \App\OAuthUser::where('oauth_id', $socialite->id)->first();

    if ($user_by_email) {
        $user = $user_by_email;
    } else if ($user_by_id) {
        $user = $user_by_id;
    } else {
        // Create User
        $user = User::create([
            'name' => $socialite->getName(),
            'email' => $socialite->getEmail(),
        ]);
    }

    $oauthUser = \App\OAuthUser::firstOrNew([
        'user_id' => $user->id,
        'oauth_id' => $socialite->getId(),
    ]);

    $oauthUser->user_id = $user->id;
    $oauthUser->oauth_id = $socialite->getId();
    $oauthUser->access_token = $socialite->token;
    $oauthUser->refresh_token = $socialite->refreshToken;
    $oauthUser->oauth_driver_id = \App\OAuthDriver::where('name', $provider)->first()->id;
    $oauthUser->save();

    return response()->json([
        'access_token' => $user->createToken('API Token')->accessToken,
    ]);
});
bnsteel's avatar

@joejordanbrown @computerfr33k

I keep getting turned around. I think I messed up the first time when I had Client use API as the redirect, so that API could get the auth from (google). I was doing great until I realized that API didn't know how to get back to Client.

So now I am having Client handle the auth with (google) and then send API the (google) token to exchange for an API token. That will let API make the socialite call to get user, and API can respond with am API token. This seems to be what the {provider}/login route does.

Does this sound like the correct tactic?

If not, is there a correct way for API to get back to Client when API is the redirect?

I'm working my way through the details on Client to test this out. I have yet to be in a place to make a call to API from Client. I don't want this to be a big burden on Client, but it seems like Client has a lot of steps.

I was getting frustrated because the socialite redirect worked great with my API, but didn't work with my client. I manually set up the auth query and redirect to (google). It would be nice if anyone had ideas of why a Socialite redirect would just sit there. It didn't look like it tried to post anything.

Thank you for your help.

bnsteel's avatar

Hello friends,

I believe I have something working, but I am not confident. I successfully completed the token handshaking and my client has an API token.

My issue now is that I am getting 404 not Found error when my route is located in the routes/api.php file, but works fine if it is in the routes/web.api file ''' Route::get('api/vi/user', function() { return response()->json(['message'=>'hooray!!']); })->middleware('auth:api');

''' My resolution was to not use the api prefix in my path name since it is automatically added to everything in the routes/api.php file. Rookie mistake.

Next, I resorted to using my controller@method action and now I'm getting an Internal server error because ''' $request->user()

''' return a null. I'm working out how to fix this, but any hints would be appreciated. I am so close!

Best

ctf0's avatar

i have an idea of the flow but not sure yet

1- go through the usual socialite auth flow (redirectToProvider / handleProviderCallback) something like https://scotch.io/tutorials/laravel-social-authentication-with-socialite

2- in the handleProviderCallback thats where you usually check for the user info in your db and decide whether to create a user or login the user or dismiss the call

3- now this is where you would normally use

Auth::login(Socialite::driver($provider)->user(), true);

to login the user, however we will instead use passport so

4- now we make an internal request to the oauth/authorize route to use Implicit Grant Tokens to get an access token, cuz we dont have the user password which is needed for the Password Grant Tokens

5- return the access token and any other info ur client app may need.

or use JWT instead and we can simplify this by

1- follow the above till #3

2- use https://github.com/tymondesigns/jwt-auth/wiki/Creating-Tokens#creating-a-token-based-on-a-user-object to create token for user, ex.

$email = Socialite::driver($provider)->user()->email;
$user  = User::whereEmail($email)->first();
$token = JWTAuth::fromUser($user);

3- now your client app on each request should add the auth header https://github.com/tymondesigns/jwt-auth/wiki/Authentication

4- on the laravel routes, we add https://github.com/tymondesigns/jwt-auth/wiki/Authentication#laravel-5-1 middlewares to check for the user token on each request

3 likes
llioor's avatar

@ctf0 perfect solution for JWT. The most easiest way!

One question please, You wrote: "the handleProviderCallback thats where you usually check for the user info in your db and decide whether to create a user or login the user or dismiss the call"

What if USER 1 registered with email and password e.g "user1@gmail.com" and then someone else, USER 2, ask to signup with social network and the response of the social network email was the email of USER 1 "user1@gmail.com"? The email is already exist in the system but I'd like to add another option to login... what do you recommend to do here?

Not all social networks make email verification and I need to trust the social network that it made an email verification process otherwise I can add the "provider_id" and "provider" to USER 1 and to let USER 2 to login as USER 1 with the social login process but because the social network didn't do verification it can be a big problem.

  1. If I will use only social networks which make email verification, it will be secured to add the provider to USER 1 and to login USER 2 as USER 1?
  2. Should I notify the user to login with the email and password and then to attache his social profile to the account?
  3. Any other advice?

*** The issue here is that I have "app clients users" which can login only via social networks and I have admins which can login via email & password and social as well. Both users (clients and admins) are on the same users table because admins can still use the app as clients app like in facebook where user can be end-user, advertiser and developer as well. In my system I have 2 roles, "admin" and "client".

Hope it was clear enough and thank you in advance! Leo.

ctf0's avatar

@llioor sorry for the late response, just noticed it now.

in case of having 2 users with the same email, there is a solution but u r still going to have issues when showing the user data, how are u going to tackle that ?

if you are not going to use the user email anywhere in ur app except for logging him in, then the solution would be.

1- create 2 records with the same email, one with providor_id and without.

2- now when the email try to login you check , is it through the social auth or the normal login form.

depend on the form the user has used to login with, u will get the id and login the user by it Auth::loginUsingId($id, true);

abellowins's avatar

You can achieve the same and not create another user model.

 Schema::create('oauth_providers', function (Blueprint $table) {
            $table->increments('id');
            $table->boolean('active')->default(0);
            $table->string('name');
            $table->string('oauth_id');
            $table->string('access_token');
            $table->string('refresh_token');
            $table->string('oauth_driver_id');
            $table->timestamps();
        });

        Schema::create('users_oauth_providers', function (Blueprint $table) {
            $table->unsignedInteger('user_id')->index();
            $table->foreign('user_id')
                ->references('id')
                ->on('users')
                ->onDelete('cascade');

            $table->unsignedInteger('oauth_provider_id')->index();
            $table->foreign('oauth_provider_id')
                ->references('id')
                ->on('oauth_providers')
                ->onDelete('cascade');

            $table->timestamps();
        });
robertkabat's avatar

History like to repeat itself I guess. I think I will try that package but I would love to understand the process. Let's assume I have this code:

/**
     * Handle the provider's callback.
     *
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function handleProviderCallback(Request $request)
    {
        try {
            // get the user from a social service provider
            $providerUser = Socialite::driver($request->provider)->stateless()->user();
        } catch (Exception $exception) {
            return $this->notFoundResponse();
        }

        // get native user
        if ($nativeUser = $this->userRepository->findExistingUserByEmail($providerUser->email)) {
            // if the native user doesn't have a social account then we need to create one
            if (!$this->userRepository->hasSocialAccountLinked($nativeUser, $request->provider)) {
                $this->userRepository->linkSocialAccount($nativeUser, $providerUser->id, $request->provider);
            }
        } else {
            // there's no native user, so we need to create it and link it to the social account
            $nativeUser = $this->userRepository->createWithRoles(
                $providerUser->nickname ??  strstr($providerUser->email, '@', true),
                $providerUser->email,
                [Role::FREE_USER_ROLE]
            );
            $this->userRepository->linkSocialAccount($nativeUser, $providerUser->id, $request->provider);
        }
        
        // add email hash
        $nativeUser->email_hash = md5($nativeUser->email);

        // return the token
        return $this->okResponse([
            'token' => $nativeUser->createToken('social-login')->accessToken,
            'user' => $nativeUser
        ]);
    }

Without any other extra package - what do I do next? If I just create token as presented in my code I will just get an access token with no refresh token.

I've set my passport settings to expire the token every 5 minutes. How do I get the refresh token in this scenario? I am really keen to understand this!

My endpoints are protected via passport API guard so google tokens and refresh tokens won't work here. How do I get passport tokens here?

Please or to participate in this conversation.