Skip to content

Instantly share code, notes, and snippets.

@AnandPilania
Last active November 18, 2024 23:39
Show Gist options
  • Save AnandPilania/43c9842276738a098ea9241985ba7559 to your computer and use it in GitHub Desktop.
Save AnandPilania/43c9842276738a098ea9241985ba7559 to your computer and use it in GitHub Desktop.
LARAVEL: Social Auth via Socialite with Breeze
...
'facebook' => [
'client_id' => env('FACEBOOK_CLIENT_ID'),
'client_secret' => env('FACEBOOK_CLIENT_SECRET'),
'redirect' => env('APP_URL').'/auth/facebook/callback',
],
'twitter' => [
'client_id' => env('TWITTER_CLIENT_ID'),
'client_secret' => env('TWITTER_CLIENT_SECRET'),
'redirect' => env('APP_URL').'/auth/twitter/callback',
],
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => env('APP_URL').'/auth/google/callback',
],
...
<?php
namespace App\Models;
use App\Traits\BelongsToUser;
use Illuminate\Database\Eloquent\Concerns\HasTimestamps;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Socialite extends Model
{
use HasFactory, HasTimestamps;
public const PROVIDERS = ['google', 'facebook', 'twitter'];
protected $fillable = [
'user_id',
'name', 'avatar',
'provider_name',
'provider_id',
'token',
'refresh_token',
'expires_at',
];
protected $hidden = [
'token',
'refresh_token'
];
protected $dates = [
'expires_at'
];
public function user()
{
return $this->belongsTo(User::class);
}
}
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\Socialite as SocialiteModel;
use App\Models\User;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Laravel\Socialite\Facades\Socialite;
class SocialiteController extends Controller
{
protected $guard;
public function redirectToProvider($provider)
{
session()->put('origin_url', back()->getTargetUrl());
if (! $this->isProviderAllowed($provider)) {
return $this->sendFailedSocialiteResponse("{$provider} provider is not currently supported!", session()->get('origin_url'));
}
try {
return Socialite::driver($provider)->redirect();
} catch (Exception $e) {
return $this->sendFailedSocialiteResponse($e->getMessage(), session()->get('origin_url'));
}
}
protected function isProviderAllowed($provider): bool
{
return in_array($provider, SocialiteModel::PROVIDERS) && config()->has("services.{$provider}");
}
protected function sendFailedSocialiteResponse($msg = null, $route = 'login'): \Illuminate\Http\RedirectResponse
{
return redirect()->route('login')->withErrors(['msg' => $msg ?: 'Unable to login, try with another provider to login.']);
}
public function handleProviderCallback(Request $request, $provider): \Illuminate\Http\RedirectResponse
{
try {
$providerAccount = Socialite::driver($provider)->user();
} catch (Exception $e) {
return $this->sendFailedSocialiteResponse($e->getMessage());
}
$account = SocialiteModel::firstWhere([
'provider_id' => $providerAccount->getId(),
'provider_name' => $provider,
]);
// Authenticated...
if (! is_null($user = Auth::user())) {
if ($account && $account->user_id !== $user->id) {
return redirect()->route('dashboard')->withErrors([
$provider.'_connect_error' => __('This :Provider sign in account is already associated with another user. Please try a different account.', ['provider' => $provider]),
]);
}
if (! $account) {
$this->createProviderForUser($user, $provider, $providerAccount);
return redirect()->route('dashboard');
}
return redirect()->route('dashboard')->withErrors([
$provider.'_connect_error' => __('This :Provider sign in account is already associated with your user.', ['provider' => $provider]),
]);
}
// Registration...
if (session()->get('origin_url') === route('register')) {
if ($account) {
return $this->sendFailedSocialiteResponse(__('An account with that :Provider sign in already exists, please login.', ['provider' => $provider]), 'register');
}
if (! $providerAccount->getEmail()) {
return $this->sendFailedSocialiteResponse(__('No email address is associated with this :Provider account. Please try a different account.', ['provider' => $provider]), 'register');
}
}
if (! $account) {
$account = $this->createProviderForUser(
User::where('email', $providerAccount->getEmail())->first() ?: $this->createNewVerifiedUser($provider, $providerAccount),
$provider,
$providerAccount
);
}
$this->loginAndUpdateUsingProvider($account->user, $provider);
return $this->sendSuccessSocialiteResponse();
}
protected function createProviderForUser(User $user, string $provider, $providerAccount): \Illuminate\Database\Eloquent\Model
{
return $user->socialite()->create([
'name' => $providerAccount->getName(),
'provider_name' => strtolower($provider),
'provider_id' => $providerAccount->getId(),
'avatar' => $providerAccount->avatar,
'token' => $providerAccount->token,
'secret' => $providerAccount->tokenSecret ?? null,
'refresh_token' => $providerAccount->refreshToken ?? null,
'expires_at' => $providerAccount->expiresAt ?? null,
]);
}
protected function createNewVerifiedUser(string $provider, $providerAccount)
{
$createdUser = User::create([
'email' => $providerAccount->getEmail(),
'using_provider' => $provider,
]);
$createdUser->markEmailAsVerified();
return $createdUser;
}
protected function loginAndUpdateUsingProvider(User $user, string $provider)
{
Auth::login($user);
if (! $user->using_provider || $user->using_provider !== $provider) {
$user->using_provider = $provider;
$user->save();
}
}
protected function sendSuccessSocialiteResponse(): \Illuminate\Http\RedirectResponse
{
return redirect()->intended(route('dashboard'));
}
}
<?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;
class User extends Authenticatable
{
use HasFactory, Notifiable;
protected $fillable = [
'name',
'email',
'password',
'avatar',
];
protected $hidden = [
'password',
'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
];
public function getNameAttribute($name = null): string
{
return $this->using_provider
? $this->currentSocialite()->name
: $name;
}
public function currentSocialite()
{
return $this->using_provider
? $this->socialite()->where('provider_name', $this->using_provider)->first()
: null;
}
public function socialite(): HasMany
{
return $this->hasMany(Socialite::class);
}
public function getAvatarAttribute($avatar = null)
{
return $this->using_provider
? $this->currentSocialite()->avatar
: ($avatar ?? '/img/placeholders/avatar.jpg');
}
}
...
Route::prefix('auth')->group(function () {
Route::get('/{provider?}', [SocialiteController::class, 'redirectToProvider'])
->name('auth.provider');
Route::get('/{provider?}/callback', [SocialiteController::class, 'handleProviderCallback'])
->name('auth.callback');
});
...
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateSocialitesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('socialites', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained();
$table->string('name');
$table->string('provider_name');
$table->string('provider_id');
$table->string('avatar')->nullable();
$table->string('token');
$table->string('secret')->nullable();
$table->string('refresh_token')->nullable();
$table->dateTime('expires_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('socialites');
}
}
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class PrepareUsersTableForSocialite extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('name')->nullable()->change();
$table->string('password')->nullable()->change();
$table->string('using_provider')->nullable()->after('password');
$table->string('avatar')->nullable()->after('using_provider');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->string('name')->nullable(false)->change();
$table->string('password')->nullable(false)->change();
$table->dropColumn('using_provider', 'avatar');
});
}
}
...
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
FACEBOOK_CLIENT_ID=
FACEBOOK_CLIENT_SECRET=
TWITTER_CLIENT_ID=
TWITTER_CLIENT_SECRET=
...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment