Using Laravel Socialite for both authentication and linking accounts
Published
in Programming
on Feb 14, 2021
It's easy to use Socialite for linking accounts, it's easy to use it for registration, but it gets a bit tricky if you want to do both. Here's how to do it.
We only want to use one OAuth2 (e.g. GitHub) application, because we don't want users to have to grant access to two separate applications.
But an application can only have one callback URL.
So, we'll build exactly that. A single callback route.
Route::get('/auth/github', function () {
return Socialite::driver('github')->redirect();
})->name('auth.github');
Route::get('/auth/github/callback', function () {
try {
$github = Socialite::driver('github')->user();
} catch (InvalidStateException $exception) {
session()->increment('github_socialite_attempt_count');
if (session('github_socialite_attempt_count') > 2) {
return redirect('/');
}
return redirect(route('auth.github'));
}
// If a user is logged in, we link the GH account
if (auth()->check()) {
/** @var User $user */
$user = auth()->user();
if ($user->isUser()) {
$user->linkGitHub($github->token, $github->getId(), $github->getNickname());
return redirect(route('profile.show'));
}
}
// If a user is not logged in, but exists, we log him in
if ($github->getId() && $user = User::firstWhere('github_id', $github->getId())) {
Auth::login($user, true);
return redirect(route('dashboard.user'));
}
// If no user is found, we create a new account
/** @var User user */
$user = User::create([
'name' => $github->getName() ?? $github->getNickname(),
'email' => $github->getEmail(),
'email_verified_at' => now()->subMinute(),
'profile_photo_path' => "https://avatars3.githubusercontent.com/u/{$github->getId()}?v=4",
]);
$user->linkGitHub($github->token, $github->getId(), $github->getNickname());
Auth::login($user, true);
return redirect(route('dashboard.user'));
})->name('auth.github.callback');
Two things:
- I'm using Jetstream and I customized my
getProfilePhotoUrl()
logic. If you use the code above, you should tweak it to match yourUser
model setup. - GitHub auth is sometimes weird and results in an "invalid state" exception, which renders as 500 in production. But it works on the second attempt. So to fix that, we just store a counter and redirect the user back to the auth route if there's an invalid state exception — for a maximum of three times.
And the method for linking accounts:
public function linkGitHub(string $token, int $id, string $username): void
{
$this->update([
'github_token' => $token,
'github_id' => $id,
'github_username' => $username,
]);
}
public function unlinkGitHub(): void
{
$this->update([
'github_token' => null,
'github_id' => null,
'github_username' => null,
]);
}
public function hasLinkedGitHub(): bool
{
return $this->github_id !== null;
}
And that's all there's to it. Now just create a button saying "Link account", make it redirect the user to /auth/github
, and it will link their account.
Similarly, create a button saying "Login with GitHub", make it redirect the user to the same route, and it will log them in, or create their account.
Comments
Elliot:
What if they are logged out, their account is not linked to GitHub and then they click register? A duplicate account is created, right?