Skip to content

Instantly share code, notes, and snippets.

@devhammed
Last active November 17, 2024 22:06
Show Gist options
  • Save devhammed/b551567fe2c0a32309d51d2c55ad9026 to your computer and use it in GitHub Desktop.
Save devhammed/b551567fe2c0a32309d51d2c55ad9026 to your computer and use it in GitHub Desktop.
Multiple OAuth Connections Support using Laravel Socialite (https://laravel.com/docs/socialite)
<?php
namespace App\Models;
use App\Enums\ConnectionProviderType;
use Illuminate\Database\Eloquent\Model;
use Database\Factories\ConnectionFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Connection extends Model
{
/**
* Get the attributes that should be cast.
*/
protected function casts(): array
{
return [
'provider_type' => ConnectionProviderType::class,
'access_token' => 'encrypted',
'refresh_token' => 'encrypted',
'metadata' => 'array',
'expires_at' => 'datetime',
];
}
/**
* The user that owns the connection.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
<?php
namespace App\Http\Controllers;
use App\Models\User;
use App\Enums\UserRole;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Auth;
use App\Enums\ConnectionProviderType;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Session;
use Laravel\Socialite\Facades\Socialite;
use Illuminate\Support\Facades\Redirect;
class ConnectionController extends Controller
{
/**
* Redirect the user to the connection page.
*/
public function redirect(ConnectionProviderType $provider): RedirectResponse
{
return Socialite::driver($provider->value)
->scopes($provider->getScopes())
->with($provider->getWith())
->redirect();
}
/**
* Obtain the user information from the connection page.
*/
public function callback(ConnectionProviderType $provider): RedirectResponse
{
$oauthUser = Socialite::driver($provider->value)->user();
$user = User::createOrFirst(
['email' => $oauthUser->getEmail()],
[
'name' => $oauthUser->getName(),
'password' => Str::password(),
'role' => UserRole::CUSTOMER,
'email_verified_at' => now(),
],
);
$accessToken = $oauthUser->token;
$refreshToken = $oauthUser->refreshToken ?? $oauthUser->tokenSecret;
$expiresAt = $oauthUser->expiresIn ? now()->addSeconds($oauthUser->expiresIn) : null;
$metadata = [
'avatar' => $oauthUser->getAvatar(),
'nickname' => $oauthUser->getNickname(),
'scopes' => $oauthUser->approvedScopes ?? $provider->getScopes(),
];
$connection = $user->connections()
->whereProviderType($provider)
->whereProviderId($oauthUser->getId())
->first();
if ($connection) {
$connection->update([
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'expires_at' => $expiresAt,
'metadata' => $metadata,
]);
} else {
$user->connections()->create([
'provider_type' => $provider,
'provider_id' => $oauthUser->getId(),
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'expires_at' => $expiresAt,
'metadata' => $metadata,
]);
}
Auth::login($user);
Session::regenerate();
return Redirect::intended($user->role->getDefaultPanel()->getUrl());
}
}
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('connections', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')
->constrained()
->restrictOnUpdate()
->restrictOnDelete();
$table->string('provider_id');
$table->string('provider_type');
$table->text('access_token')
->nullable();
$table->text('refresh_token')
->nullable();
$table->timestamp('expires_at')
->nullable();
$table->json('metadata');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('connections');
}
};
<?php
namespace App\Enums;
use Filament\Support\Contracts\HasIcon;
enum ConnectionProviderType: string implements HasIcon
{
case GOOGLE = 'google';
public function getScopes(): array
{
return match ($this) {
self::GOOGLE => [],
};
}
public function getWith(): array
{
return match ($this) {
self::GOOGLE => [
'access_type' => 'offline',
],
};
}
public function getLoginLabel(): ?string
{
return match ($this) {
self::GOOGLE => t('Continue with Google'),
};
}
public function getIcon(): ?string
{
return match ($this) {
self::GOOGLE => 'google',
};
}
public static function getLoginProviders(): array
{
return [
self::GOOGLE,
];
}
}
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ConnectionController;
Route::get('/connections/{provider}', [ConnectionController::class, 'redirect'])
->name('connections.redirect');
Route::get('/connections/{provider}/callback', [ConnectionController::class, 'callback'])
->name('connections.callback');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment