Hi guys
Looking to start a conversation on best practices when using Socialite, developing in a vacuum can be dangerous so I’d like this post to be a conversation.
This first post I’ve got a couple questions/topics I’d like to chat about, all focused on Laravel Socialite and extending to the 3rd party providers.
Authentication callback, Controller v. Closure
The first topic I’d like to pass by the community is whether you prefer handling the callback from the provider via a controller or within a route closure. I’ve included an example of my “handleProviderCallback()” method below.
// ProvidersController.php
namespace App\Http\Controllers\Auth;
use App\AccountService;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Laravel\Socialite\Facades\Socialite;
class ProvidersController extends Controller
{
public function redirectToProvider($provider){...}
public function handleProviderCallback(AccountService $accountService, $provider)
{
if (request('error')) {
abort(403, request('error_description'));
}
/**
* Try getting the user from the provider, if it doesn't work have them login again.
*/
try {
$providerUser = Socialite::driver($provider)->user();
} catch (\Throwable $th) {
return redirect()->route('home');
}
/**
* Use the account service to find or create the user to login.
*
* Note: the account service code is located below in another question.
*/
$authUser = $accountService->findOrCreate($providerUser, $provider);
Auth::login($authUser);
return redirect()->intended('/');
}
}
Account Service to Handle Finding or Creating a User
The second question is around utilizing an external class called the “AccountService” to find or create a user. I picked up this method from an article a couple of years ago and it’s something I think could be improved.
In a nutshell it handles returning an existing user if it already exists, returning an error if the email address associated with the provider user exists and then creating the new user and returning it.
// AccountService.php
namespace App;
use App\Models\LinkedProvider;
use App\Models\User;
use laravel\Socialite\Contracts\User as ProviderUser;
class AccountService
{
public $user;
public function findOrCreate(ProviderUser $providerUser, $provider)
{
$account = LinkedProvider::with('user')
->firstWhere([
['provider_name', '=', $provider],
['provider_id', '=', $providerUser->getId()],
]);
if ($account && $account->has('user'))
{
return $account->user;
}
if (User::firstWhere('email', $providerUser->getEmail())) {
abort(500, 'Nope, user already exists with that email.');
}
$this->user = User::create([
'name' => $providerUser->getName(),
'email' => $providerUser->getEmail(),
]);
$this->user->linkedProviders()->create([
'provider_name' => $provider,
'provider_id' => $providerUser->getId(),
]);
return $this->user;
}
}
Looking forward to chatting and getting feedback on how I can improve this process.