├── resources ├── views │ ├── .gitkeep │ └── components │ │ └── buttons.blade.php ├── css │ └── plugin.css ├── lang │ └── en │ │ └── auth.php └── dist │ └── plugin.css ├── tailwind.config.js ├── bin └── upgrade-v2 ├── src ├── Exceptions │ ├── ImplementationException.php │ ├── GuardNotStateful.php │ ├── ProviderNotConfigured.php │ └── InvalidCallbackPayload.php ├── Events │ ├── InvalidState.php │ ├── UserNotAllowed.php │ ├── Login.php │ ├── RegistrationNotEnabled.php │ ├── Registered.php │ └── SocialiteUserConnected.php ├── Models │ ├── Contracts │ │ └── FilamentSocialiteUser.php │ └── SocialiteUser.php ├── Traits │ ├── CanBeHidden.php │ ├── Routes.php │ ├── Models.php │ └── Callbacks.php ├── View │ └── Components │ │ └── Buttons.php ├── Http │ ├── Middleware │ │ └── PanelFromUrlQuery.php │ └── Controllers │ │ └── SocialiteLoginController.php ├── FilamentSocialiteServiceProvider.php ├── Provider.php └── FilamentSocialitePlugin.php ├── package.json ├── SECURITY.md ├── config └── filament-socialite.php ├── database └── migrations │ └── create_socialite_users_table.php.stub ├── phpunit.xml ├── LICENSE.md ├── routes └── web.php ├── composer.json ├── rector.php ├── UPGRADE.md ├── .php-cs-fixer.php ├── CHANGELOG.md └── README.md /resources/views/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/css/plugin.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | 3 | @config '../../tailwind.config.js'; 4 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ["./resources/views/**/*.blade.php"], 3 | corePlugins: { 4 | preflight: false, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /bin/upgrade-v2: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 'Or log in via', 5 | 6 | 'login-failed' => 'Login failed, please try again.', 7 | 8 | 'user-not-allowed' => 'Your email is not part of a domain that is allowed.', 9 | 10 | 'registration-not-enabled' => 'Registration of a new user is not allowed.', 11 | ]; 12 | -------------------------------------------------------------------------------- /src/Exceptions/GuardNotStateful.php: -------------------------------------------------------------------------------- 1 | [ 15 | \Illuminate\Cookie\Middleware\EncryptCookies::class, 16 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 17 | \Illuminate\Session\Middleware\StartSession::class, 18 | \Illuminate\Session\Middleware\AuthenticateSession::class, 19 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 20 | ], 21 | ]; 22 | -------------------------------------------------------------------------------- /src/Events/Registered.php: -------------------------------------------------------------------------------- 1 | id(); 12 | 13 | $table->foreignId('user_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate(); 14 | $table->string('provider'); 15 | $table->string('provider_id'); 16 | 17 | $table->timestamps(); 18 | 19 | $table->unique([ 20 | 'provider', 21 | 'provider_id', 22 | ]); 23 | }); 24 | } 25 | 26 | public function down() 27 | { 28 | Schema::dropIfExists('socialite_users'); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/Traits/CanBeHidden.php: -------------------------------------------------------------------------------- 1 | isHidden = $condition; 16 | 17 | return $this; 18 | } 19 | 20 | public function visible(bool | Closure $condition = true): static 21 | { 22 | $this->isVisible = $condition; 23 | 24 | return $this; 25 | } 26 | 27 | public function isHidden(): bool 28 | { 29 | if ($this->evaluate($this->isHidden)) { 30 | return true; 31 | } 32 | 33 | return ! $this->evaluate($this->isVisible); 34 | } 35 | 36 | public function isVisible(): bool 37 | { 38 | return ! $this->isHidden(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | tests 10 | 11 | 12 | 13 | 14 | src 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/Traits/Routes.php: -------------------------------------------------------------------------------- 1 | getPanel()->generateRouteName('oauth.redirect')}"; 14 | } 15 | 16 | public function loginRouteName(string $value): static 17 | { 18 | $this->loginRouteName = $value; 19 | 20 | return $this; 21 | } 22 | 23 | public function getLoginRouteName(): string 24 | { 25 | return $this->loginRouteName ?? $this->getPanel()->generateRouteName('auth.login'); 26 | } 27 | 28 | public function dashboardRouteName(string $value): static 29 | { 30 | $this->dashboardRouteName = $value; 31 | 32 | return $this; 33 | } 34 | 35 | public function getDashboardRouteName(): string 36 | { 37 | return $this->dashboardRouteName ?? $this->getPanel()->generateRouteName('pages.dashboard'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) DutchCodingCompany 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/View/Components/Buttons.php: -------------------------------------------------------------------------------- 1 | plugin = FilamentSocialitePlugin::current(); 18 | } 19 | 20 | /** 21 | * @inheritDoc 22 | */ 23 | public function render() 24 | { 25 | $messageBag = new MessageBag(); 26 | 27 | if (session()->has('filament-socialite-login-error')) { 28 | $messageBag->add('login-failed', session()->pull('filament-socialite-login-error')); 29 | } 30 | 31 | return view('filament-socialite::components.buttons', [ 32 | 'providers' => $providers = $this->plugin->getProviders(), 33 | 'visibleProviders' => array_filter($providers, fn (Provider $provider) => $provider->isVisible()), 34 | 'socialiteRoute' => $this->plugin->getRoute(), 35 | 'messageBag' => $messageBag, 36 | ]); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Http/Middleware/PanelFromUrlQuery.php: -------------------------------------------------------------------------------- 1 | handle($request, $next, static::decrypt($request)); 23 | } 24 | 25 | public static function encrypt(string $panel): string 26 | { 27 | return Crypt::encrypt($panel); 28 | } 29 | 30 | /** 31 | * @throws InvalidCallbackPayload 32 | */ 33 | public static function decrypt(Request $request): string 34 | { 35 | try { 36 | if (! is_string($request->get('state'))) { 37 | throw new DecryptException('State is not a string.'); 38 | } 39 | 40 | return Crypt::decrypt($request->get('state')); 41 | } catch (DecryptException $e) { 42 | throw InvalidCallbackPayload::make($e); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /resources/views/components/buttons.blade.php: -------------------------------------------------------------------------------- 1 |
5 |
6 | @if ($messageBag->isNotEmpty()) 7 | @foreach($messageBag->all() as $value) 8 |

{{ __($value) }}

9 | @endforeach 10 | @endif 11 | 12 | @if (count($visibleProviders)) 13 | @if($showDivider) 14 |
15 |
16 |

17 | {{ __('filament-socialite::auth.login-via') }} 18 |

19 |
20 | @endif 21 | 22 |
23 | @foreach($visibleProviders as $key => $provider) 24 | 32 | {{ $provider->getLabel() }} 33 | 34 | @endforeach 35 |
36 | @else 37 | 38 | @endif 39 |
40 |
41 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | hasPlugin('filament-socialite')) { 10 | continue; 11 | } 12 | 13 | // Retrieve slug for route name. 14 | $slug = $panel->getPlugin('filament-socialite')->getSlug(); 15 | 16 | $domains = $panel->getDomains(); 17 | 18 | foreach ((empty($domains) ? [null] : $domains) as $domain) { 19 | Filament::currentDomain($domain); 20 | 21 | Route::domain($domain) 22 | ->middleware($panel->getMiddleware()) 23 | ->name("socialite.{$panel->generateRouteName('oauth.redirect')}") 24 | ->get("/$slug/oauth/{provider}", [SocialiteLoginController::class, 'redirectToProvider']); 25 | 26 | Route::domain($domain) 27 | ->match(['get', 'post'], "$slug/oauth/callback/{provider}", [SocialiteLoginController::class, 'processCallback']) 28 | ->middleware([ 29 | ...$panel->getMiddleware(), 30 | ...config('filament-socialite.middleware'), 31 | ]) 32 | ->name("socialite.{$panel->generateRouteName('oauth.callback')}"); 33 | 34 | Filament::currentDomain(null); 35 | } 36 | } 37 | 38 | /** 39 | * @note This route can only distinguish between Filament panels using the `state` input. If you have a stateless OAuth 40 | * implementation, use the "$slug/oauth/callback/{provider}" route instead which has the panel in the URL itself. 41 | */ 42 | Route::match(['get', 'post'], "/oauth/callback/{provider}", [SocialiteLoginController::class, 'processCallback']) 43 | ->middleware([ 44 | PanelFromUrlQuery::class, 45 | ...config('filament-socialite.middleware'), 46 | ]) 47 | ->name('oauth.callback'); 48 | -------------------------------------------------------------------------------- /src/Traits/Models.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected string $userModelClass = \App\Models\User::class; 15 | 16 | /** 17 | * @var class-string<\DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser> 18 | */ 19 | protected string $socialiteUserModelClass = SocialiteUser::class; 20 | 21 | /** 22 | * @param class-string<\Illuminate\Contracts\Auth\Authenticatable> $value 23 | */ 24 | public function userModelClass(string $value): static 25 | { 26 | $this->userModelClass = $value; 27 | 28 | return $this; 29 | } 30 | 31 | /** 32 | * @return class-string<\Illuminate\Contracts\Auth\Authenticatable> 33 | */ 34 | public function getUserModelClass(): string 35 | { 36 | return $this->userModelClass; 37 | } 38 | 39 | /** 40 | * @param class-string<\DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser> $value 41 | */ 42 | public function socialiteUserModelClass(string $value): static 43 | { 44 | $this->socialiteUserModelClass = $value; 45 | 46 | return $this; 47 | } 48 | 49 | /** 50 | * @return class-string<\DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser> 51 | */ 52 | public function getSocialiteUserModelClass(): string 53 | { 54 | return $this->socialiteUserModelClass; 55 | } 56 | 57 | public function getSocialiteUserModel(): FilamentSocialiteUserContract 58 | { 59 | return new ($this->getSocialiteUserModelClass()); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Models/SocialiteUser.php: -------------------------------------------------------------------------------- 1 | 27 | */ 28 | public function user(): BelongsTo 29 | { 30 | /** @var class-string<\Illuminate\Database\Eloquent\Model&\Illuminate\Contracts\Auth\Authenticatable> */ 31 | $user = FilamentSocialitePlugin::current()->getUserModelClass(); 32 | 33 | return $this->belongsTo($user); 34 | } 35 | 36 | public function getUser(): Authenticatable 37 | { 38 | assert($this->user instanceof Authenticatable); 39 | 40 | return $this->user; 41 | } 42 | 43 | public static function findForProvider(string $provider, SocialiteUserContract $oauthUser): ?self 44 | { 45 | return self::query() 46 | ->where('provider', $provider) 47 | ->where('provider_id', $oauthUser->getId()) 48 | ->first(); 49 | } 50 | 51 | public static function createForProvider(string $provider, SocialiteUserContract $oauthUser, Authenticatable $user): self 52 | { 53 | return self::query() 54 | ->create([ 55 | 'user_id' => $user->getKey(), 56 | 'provider' => $provider, 57 | 'provider_id' => $oauthUser->getId(), 58 | ]); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/FilamentSocialiteServiceProvider.php: -------------------------------------------------------------------------------- 1 | name('filament-socialite') 21 | ->hasConfigFile() 22 | ->hasTranslations() 23 | ->hasViews() 24 | ->hasRoute('web') 25 | ->hasMigration('create_socialite_users_table'); 26 | } 27 | 28 | public function packageRegistered(): void 29 | { 30 | // 31 | } 32 | 33 | public function packageBooted(): void 34 | { 35 | Blade::componentNamespace('DutchCodingCompany\FilamentSocialite\View\Components', 'filament-socialite'); 36 | Blade::component('buttons', Buttons::class); 37 | 38 | FilamentAsset::register([ 39 | Css::make('filament-socialite-styles', __DIR__.'/../resources/dist/plugin.css')->loadedOnRequest(), 40 | ], package: 'filament-socialite'); 41 | 42 | FilamentView::registerRenderHook( 43 | 'panels::auth.login.form.after', 44 | static function (): ?string { 45 | $panel = Filament::getCurrentPanel(); 46 | 47 | if (! $panel?->hasPlugin('filament-socialite')) { 48 | return null; 49 | } 50 | 51 | /** @var \DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin */ 52 | $plugin = $panel->getPlugin('filament-socialite'); 53 | 54 | return Blade::render(''); 55 | }, 56 | ); 57 | 58 | if ( 59 | version_compare(app()->version(), '11.0', '>=') 60 | && method_exists(VerifyCsrfToken::class, 'except') 61 | ) { 62 | VerifyCsrfToken::except([ 63 | '*/oauth/callback/*', 64 | 'oauth/callback/*', 65 | ]); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dutchcodingcompany/filament-socialite", 3 | "description": "Social login for Filament through Laravel Socialite", 4 | "keywords": [ 5 | "DutchCodingCompany", 6 | "laravel", 7 | "filament-socialite" 8 | ], 9 | "homepage": "https://github.com/dutchcodingcompany/filament-socialite", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Marco Boers", 14 | "email": "m@rcoboe.rs", 15 | "role": "Developer" 16 | }, 17 | { 18 | "name": "Tom Janssen", 19 | "email": "dododedodonl@thor.edu", 20 | "role": "Developer" 21 | }, 22 | { 23 | "name": "Bram Raaijmakers", 24 | "email": "bram@dutchcodingcompany.com", 25 | "role": "Developer" 26 | } 27 | ], 28 | "require": { 29 | "php": "^8.2", 30 | "filament/filament": "^4.0", 31 | "illuminate/contracts": "^11.0|^12.0", 32 | "laravel/socialite": "^5.5", 33 | "spatie/laravel-package-tools": "^1.9.2" 34 | }, 35 | "require-dev": { 36 | "friendsofphp/php-cs-fixer": "^3.8", 37 | "larastan/larastan": "^2.9|^3.0", 38 | "nunomaduro/collision": "^7.0|^8.1", 39 | "orchestra/testbench": "^9.0|^10.0", 40 | "phpunit/phpunit": "^10.0|^11.5.3", 41 | "rector/rector": "^0.19.8|^2.0" 42 | }, 43 | "suggest": { 44 | "owenvoke/blade-fontawesome": "^2.0" 45 | }, 46 | "autoload": { 47 | "psr-4": { 48 | "DutchCodingCompany\\FilamentSocialite\\": "src", 49 | "DutchCodingCompany\\FilamentSocialite\\Tests\\": "tests", 50 | "DutchCodingCompany\\FilamentSocialite\\Database\\Factories\\": "database/factories", 51 | "Utils\\Rector\\": "utils/rector/src" 52 | } 53 | }, 54 | "scripts": { 55 | "analyse": "vendor/bin/phpstan analyse", 56 | "test": "vendor/bin/phpunit" 57 | }, 58 | "bin": [ 59 | "bin/upgrade-v2" 60 | ], 61 | "config": { 62 | "sort-packages": true, 63 | "allow-plugins": { 64 | "phpstan/extension-installer": true 65 | } 66 | }, 67 | "extra": { 68 | "laravel": { 69 | "providers": [ 70 | "DutchCodingCompany\\FilamentSocialite\\FilamentSocialiteServiceProvider" 71 | ], 72 | "aliases": { 73 | "FilamentSocialite": "DutchCodingCompany\\FilamentSocialite\\Facades\\FilamentSocialite" 74 | } 75 | } 76 | }, 77 | "minimum-stability": "dev", 78 | "prefer-stable": true, 79 | "autoload-dev": { 80 | "psr-4": { 81 | "DutchCodingCompany\\FilamentSocialite\\Tests\\": "tests" 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | paths([ 10 | 'app/Providers/Filament/', 11 | ]); 12 | 13 | $rectorConfig->ruleWithConfiguration( 14 | \Rector\Renaming\Rector\MethodCall\RenameMethodRector::class, 15 | [ 16 | new \Rector\Renaming\ValueObject\MethodCallRename( 17 | "DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin", 18 | "setProviders", 19 | "providers", 20 | ), 21 | new \Rector\Renaming\ValueObject\MethodCallRename( 22 | "DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin", 23 | "setRegistrationEnabled", 24 | "registration", 25 | ), 26 | new \Rector\Renaming\ValueObject\MethodCallRename( 27 | "DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin", 28 | "setSlug", 29 | "slug", 30 | ), 31 | new \Rector\Renaming\ValueObject\MethodCallRename( 32 | "DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin", 33 | "setLoginRouteName", 34 | "loginRouteName", 35 | ), 36 | new \Rector\Renaming\ValueObject\MethodCallRename( 37 | "DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin", 38 | "setDashboardRouteName", 39 | "dashboardRouteName", 40 | ), 41 | new \Rector\Renaming\ValueObject\MethodCallRename( 42 | "DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin", 43 | "setRememberLogin", 44 | "rememberLogin", 45 | ), 46 | new \Rector\Renaming\ValueObject\MethodCallRename( 47 | "DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin", 48 | "setSocialiteUserModelClass", 49 | "socialiteUserModelClass", 50 | ), 51 | new \Rector\Renaming\ValueObject\MethodCallRename( 52 | "DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin", 53 | "setDomainAllowList", 54 | "domainAllowList", 55 | ), 56 | new \Rector\Renaming\ValueObject\MethodCallRename( 57 | "DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin", 58 | "setUserModelClass", 59 | "userModelClass", 60 | ), 61 | new \Rector\Renaming\ValueObject\MethodCallRename( 62 | "DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin", 63 | "setShowDivider", 64 | "showDivider", 65 | ), 66 | ] 67 | ); 68 | }; 69 | -------------------------------------------------------------------------------- /src/Provider.php: -------------------------------------------------------------------------------- 1 | | array 31 | */ 32 | protected Closure | array $scopes = []; 33 | 34 | /** 35 | * @var \Closure(): array | array 36 | */ 37 | protected Closure | array $with = []; 38 | 39 | protected bool $stateless = false; 40 | 41 | public function __construct(string $name) 42 | { 43 | $this->name($name); 44 | } 45 | 46 | public static function make(string $name): static 47 | { 48 | return app(static::class, ['name' => $name]); 49 | } 50 | 51 | /** 52 | * @param array $attributes 53 | */ 54 | public function fill(array $attributes): static 55 | { 56 | foreach ($attributes as $key => $value) { 57 | $this->{$key}($value); 58 | } 59 | 60 | return $this; 61 | } 62 | 63 | public function name(string $name): static 64 | { 65 | $this->name = $name; 66 | 67 | return $this; 68 | } 69 | 70 | public function getName(): string 71 | { 72 | return $this->name; 73 | } 74 | 75 | public function label(string $label): static 76 | { 77 | $this->label = $label; 78 | 79 | return $this; 80 | } 81 | 82 | public function getLabel(): string 83 | { 84 | return $this->label ?? Str::title($this->getName()); 85 | } 86 | 87 | public function icon(string | null $icon): static 88 | { 89 | $this->icon = $icon; 90 | 91 | return $this; 92 | } 93 | 94 | public function getIcon(): string | null 95 | { 96 | return $this->icon; 97 | } 98 | 99 | /** 100 | * @param string | array{50: string, 100: string, 200: string, 300: string, 400: string, 500: string, 600: string, 700: string, 800: string, 900: string, 950: string} | null $color 101 | */ 102 | public function color(string | array | null $color): static 103 | { 104 | $this->color = $color; 105 | 106 | return $this; 107 | } 108 | 109 | /** 110 | * @return string | array{50: string, 100: string, 200: string, 300: string, 400: string, 500: string, 600: string, 700: string, 800: string, 900: string, 950: string} | null 111 | */ 112 | public function getColor(): string | array | null 113 | { 114 | return $this->color; 115 | } 116 | 117 | public function outlined(bool $outlined = true): static 118 | { 119 | $this->outlined = $outlined; 120 | 121 | return $this; 122 | } 123 | 124 | public function getOutlined(): bool 125 | { 126 | return $this->outlined; 127 | } 128 | 129 | /** 130 | * @param Closure(): array | array $scopes 131 | */ 132 | public function scopes(Closure | array $scopes): static 133 | { 134 | $this->scopes = $scopes; 135 | 136 | return $this; 137 | } 138 | 139 | /** 140 | * @return array 141 | */ 142 | public function getScopes(): array 143 | { 144 | return $this->evaluate($this->scopes, ['provider' => $this]); 145 | } 146 | 147 | /** 148 | * @param Closure(): array | array $with 149 | */ 150 | public function with(Closure | array $with): static 151 | { 152 | $this->with = $with; 153 | 154 | return $this; 155 | } 156 | 157 | /** 158 | * @return array 159 | */ 160 | public function getWith(): array 161 | { 162 | return $this->evaluate($this->with, ['provider' => $this]); 163 | } 164 | 165 | public function stateless(bool $stateless = true): static 166 | { 167 | $this->stateless = $stateless; 168 | 169 | return $this; 170 | } 171 | 172 | public function getStateless(): bool 173 | { 174 | return $this->stateless; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /resources/dist/plugin.css: -------------------------------------------------------------------------------- 1 | /*! tailwindcss v4.1.11 | MIT License | https://tailwindcss.com */ 2 | @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-font-weight:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-800:oklch(27.8% .033 256.848);--color-white:#fff;--spacing:.25rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--font-weight-medium:500;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.absolute{position:absolute}.relative{position:relative}.static{position:static}.contents{display:contents}.flex{display:flex}.grid{display:grid}.inline-block{display:inline-block}.table{display:table}.h-px{height:1px}.w-full{width:100%}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.gap-4{gap:calc(var(--spacing)*4)}.gap-y-6{row-gap:calc(var(--spacing)*6)}.rounded-full{border-radius:3.40282e38px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-gray-200{border-color:var(--color-gray-200)}.bg-white{background-color:var(--color-white)}.p-2{padding:calc(var(--spacing)*2)}.text-center{text-align:center}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.text-gray-500{color:var(--color-gray-500)}@media (prefers-color-scheme:dark){.dark\:bg-gray-800{background-color:var(--color-gray-800)}.dark\:text-gray-100{color:var(--color-gray-100)}}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false} -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | # Upgrade Guide 2 | ## `2.x.x` to `3.x.x` (Filament v4.x) 3 | 4 | ### Slug 5 | In v3 of the plugin, the panel's configured path is used instead of it's ID when generating the callback URLs. 6 | In order to revert to previous behaviour, use slug to override the behaviour: 7 | ```php 8 | ->plugin( 9 | FilamentSocialitePlugin::make() 10 | ->slug('admin') // change this to the panel's ID 11 | // other config for plugin 12 | ) 13 | ``` 14 | 15 | ## `1.x.x` to `2.x.x` (Filament v3.x) 16 | 17 | For version 2 we refactored most of the plugin to be more consistent with the Filament naming conventions. We've also moved some of the callbacks to the plugin, so they are configurable per panel. 18 | 19 | ### Method names 20 | 21 | Every method name has been changed to be more consistent with the Filament naming conventions. The following changes have been made: 22 | 23 | - `setProviders()` -> `providers()` 24 | - `setSlug()` -> `slug()` 25 | - `setLoginRouteName()` -> `loginRouteName()` 26 | - `setDashboardRouteName()` -> `dashboardRouteName()` 27 | - `setRememberLogin()` -> `rememberLogin()` 28 | - `setRegistrationEnabled()` -> `registration()` 29 | - `getRegistrationEnabled()` -> `getRegistration()` 30 | - `setDomainAllowList()` -> `domainAllowList()` 31 | - `setSocialiteUserModelClass()` -> `socialiteUserModelClass()` 32 | - `setUserModelClass()` -> `userModelClass()` 33 | - `setShowDivider()` -> `showDivider()` 34 | 35 | **Note:** We've included a simple rector script which automatically updates the method names. It checks all panel providers in the `app\Provider\Filament` directory. You can run the script by executing the following command: 36 | 37 | ```bash 38 | vendor/bin/upgrade-v2 39 | ``` 40 | #### Callbacks 41 | 42 | **setCreateUserCallback()** 43 | 44 | The `setCreateUserCallback()` has been renamed to `createUserUsing()`. This function was first registered in the `boot` method of your `AppServiceProvider.php`, but now it should be called on the plugin. 45 | 46 | ```php 47 | FilamentSocialitePlugin::make() 48 | // ... 49 | ->createUserUsing(function (string $provider, SocialiteUserContract $oauthUser, FilamentSocialitePlugin $plugin) { 50 | // Logic to create a new user. 51 | }) 52 | ``` 53 | 54 | **setUserResolver()** 55 | 56 | The `setUserResolver()` has been renamed to `resolveUserUsing()`. This function was first registered in the `boot` method of your `AppServiceProvider.php`, but now it should be called on the plugin. 57 | 58 | ```php 59 | FilamentSocialitePlugin::make() 60 | // ... 61 | ->resolveUserUsing(function (string $provider, SocialiteUserContract $oauthUser, FilamentSocialitePlugin $plugin) { 62 | // Logic to retrieve an existing user. 63 | }) 64 | ``` 65 | 66 | **setLoginRedirectCallback()** 67 | 68 | The `setLoginRedirectCallback()` has been renamed to `redirectAfterLoginUsing()`. This function was first registered in the `boot` method of your `AppServiceProvider.php`, but now it should be called on the plugin. 69 | 70 | ```php 71 | FilamentSocialitePlugin::make() 72 | // ... 73 | ->redirectAfterLoginUsing(function (string $provider, FilamentSocialiteUserContract $socialiteUser, FilamentSocialitePlugin $plugin) { 74 | // Change the redirect behaviour here. 75 | }) 76 | ``` 77 | 78 | #### Removals 79 | 80 | **getOptionalParameters()** 81 | 82 | This function was used internally only inside the `SocialiteLoginController`. If you haven't extended this controller, you can ignore this change. 83 | 84 | Provider details can now be retrieved using `$plugin->getProvider($provider)->getWith()`. 85 | 86 | **getProviderScopes()** 87 | 88 | This function was used internally only inside the `SocialiteLoginController`. If you haven't extended this controller, you can ignore this change. 89 | 90 | Provider details can now be retrieved using `$plugin->getProvider($provider)->getScopes()`. 91 | 92 | ### Configuration 93 | 94 | **Providers** 95 | 96 | Previously, providers were configured by passing a plain array. In the new setup, they should be created using the `Provider` class. The key should be passed as part of the `make()` function. 97 | 98 | ```php 99 | use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin; 100 | use DutchCodingCompany\FilamentSocialite\Provider; 101 | 102 | FilamentSocialitePlugin::make() 103 | ->providers([ 104 | Provider::make('gitlab') 105 | ->label('GitLab') 106 | ->icon('fab-gitlab') 107 | ->color(Color::hex('#2f2a6b')), 108 | ]), 109 | ``` 110 | 111 | **Scopes and Optional parameters** 112 | 113 | Scopes and additional parameters for Socialite providers were previously configured in the `services.php` file, but have now been moved to the `->providers()` method on the Filament plugin. 114 | 115 | ```php 116 | use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin; 117 | use DutchCodingCompany\FilamentSocialite\Provider; 118 | 119 | FilamentSocialitePlugin::make() 120 | ->providers([ 121 | Provider::make('gitlab') 122 | // ... 123 | ->scopes([ 124 | // Add scopes here. 125 | 'read:user', 126 | 'public_repo', 127 | ]), 128 | ->with([ 129 | // Add optional parameters here. 130 | 'hd' => 'example.com', 131 | ]), 132 | ]), 133 | ``` 134 | 135 | ## `0.x.x` to `1.x.x` (Filament v3.x) 136 | - Replace/republish the configuration file: 137 | - `sail artisan vendor:publish --provider="DutchCodingCompany\FilamentSocialite\FilamentSocialiteServiceProvider"` 138 | - Update your panel configuration `App\Providers\Filament\YourPanelProvider` to include the plugin: 139 | - Append `->plugins([\DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin::make()])` 140 | - Configure any options by chaining functions on the plugin. 141 | 142 | ## `0.x.x` (Filament v2.x) 143 | - Initial version 144 | -------------------------------------------------------------------------------- /src/FilamentSocialitePlugin.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | protected array $providers = []; 28 | 29 | /** 30 | * @var array 31 | */ 32 | protected array $domainAllowList = []; 33 | 34 | protected bool $rememberLogin = false; 35 | 36 | /** 37 | * @phpstan-var (\Closure(string $provider, \Laravel\Socialite\Contracts\User $oauthUser, ?\Illuminate\Contracts\Auth\Authenticatable $user): bool) | bool 38 | */ 39 | protected Closure | bool $registration = false; 40 | 41 | protected ?string $slug = null; 42 | 43 | protected ?string $panelId = null; 44 | 45 | protected bool $showDivider = true; 46 | 47 | public function __construct( 48 | protected Repository $config, 49 | protected Factory $auth, 50 | ) { 51 | // 52 | } 53 | 54 | public static function make(): static 55 | { 56 | return app(static::class); 57 | } 58 | 59 | public static function current(): static 60 | { 61 | if (Filament::getCurrentPanel()?->hasPlugin('filament-socialite')) { 62 | /** @var static $plugin */ 63 | $plugin = Filament::getCurrentPanel()->getPlugin('filament-socialite'); 64 | 65 | return $plugin; 66 | } 67 | 68 | throw new ImplementationException('No current panel found with filament-socialite plugin.'); 69 | } 70 | 71 | public function getId(): string 72 | { 73 | return 'filament-socialite'; 74 | } 75 | 76 | public function register(Panel $panel): void 77 | { 78 | $this->panelId = $panel->getId(); 79 | } 80 | 81 | public function boot(Panel $panel): void 82 | { 83 | // 84 | } 85 | 86 | /** 87 | * @param array $providers 88 | */ 89 | public function providers(array $providers): static 90 | { 91 | // Assign providers as key-value pairs with the provider name as the key. 92 | $this->providers = Arr::mapWithKeys( 93 | $providers, 94 | static fn (Provider $value) => [$value->getName() => $value], 95 | ); 96 | 97 | return $this; 98 | } 99 | 100 | /** 101 | * @return array 102 | */ 103 | public function getProviders(): array 104 | { 105 | return $this->providers; 106 | } 107 | 108 | public function getProvider(string $provider): Provider 109 | { 110 | if (! $this->isProviderConfigured($provider)) { 111 | throw ProviderNotConfigured::make($provider); 112 | } 113 | 114 | return $this->providers[$provider]; 115 | } 116 | 117 | public function slug(?string $slug): static 118 | { 119 | $this->slug = $slug; 120 | 121 | return $this; 122 | } 123 | 124 | public function getSlug(): string 125 | { 126 | return $this->slug ?? rtrim($this->getPanel()->getPath(), '/'); 127 | } 128 | 129 | public function rememberLogin(bool $value): static 130 | { 131 | $this->rememberLogin = $value; 132 | 133 | return $this; 134 | } 135 | 136 | public function getRememberLogin(): bool 137 | { 138 | return $this->rememberLogin; 139 | } 140 | 141 | /** 142 | * @param (\Closure(string $provider, \Laravel\Socialite\Contracts\User $oauthUser, ?\Illuminate\Contracts\Auth\Authenticatable $user): bool) | bool $value 143 | * @return $this 144 | */ 145 | public function registration(Closure | bool $value = true): static 146 | { 147 | $this->registration = $value; 148 | 149 | return $this; 150 | } 151 | 152 | /** 153 | * @return (\Closure(string $provider, \Laravel\Socialite\Contracts\User $oauthUser, ?\Illuminate\Contracts\Auth\Authenticatable $user): bool) | bool 154 | */ 155 | public function getRegistration(): Closure | bool 156 | { 157 | return $this->registration; 158 | } 159 | 160 | /** 161 | * @param array $values 162 | */ 163 | public function domainAllowList(array $values): static 164 | { 165 | $this->domainAllowList = $values; 166 | 167 | return $this; 168 | } 169 | 170 | /** 171 | * @return array 172 | */ 173 | public function getDomainAllowList(): array 174 | { 175 | return $this->domainAllowList; 176 | } 177 | 178 | public function isProviderConfigured(string $provider): bool 179 | { 180 | return $this->config->has('services.'.$provider) && isset($this->providers[$provider]); 181 | } 182 | 183 | public function showDivider(bool $divider): static 184 | { 185 | $this->showDivider = $divider; 186 | 187 | return $this; 188 | } 189 | 190 | public function getShowDivider(): bool 191 | { 192 | return $this->showDivider; 193 | } 194 | 195 | public function getPanel(): Panel 196 | { 197 | return Filament::getPanel($this->getPanelId()); 198 | } 199 | 200 | public function getPanelId(): string 201 | { 202 | return $this->panelId ?? throw new ImplementationException('Panel ID not set.'); 203 | } 204 | 205 | public function getGuard(): StatefulGuard 206 | { 207 | $guard = $this->auth->guard( 208 | $guardName = $this->getPanel()->getAuthGuard() 209 | ); 210 | 211 | if ($guard instanceof StatefulGuard) { 212 | return $guard; 213 | } 214 | 215 | throw GuardNotStateful::make($guardName); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /.php-cs-fixer.php: -------------------------------------------------------------------------------- 1 | true, 8 | 'array_syntax' => ['syntax' => 'short'], 9 | 'binary_operator_spaces' => [ 10 | 'default' => 'single_space', 11 | // 'operators' => ['=>' => null], // single space makes code look more coherent in style. But sometimes it is not beter, in that case, manually override. 12 | ], 13 | 'blank_line_after_namespace' => true, 14 | 'blank_line_after_opening_tag' => true, 15 | 'blank_line_before_statement' => [ 16 | 'statements' => ['return'], 17 | ], 18 | 'braces' => true, 19 | 'cast_spaces' => true, 20 | 'class_attributes_separation' => [ 21 | 'elements' => [ 22 | 'const' => 'only_if_meta', 23 | 'method' => 'one', 24 | 'property' => 'one', 25 | 'trait_import' => 'none', 26 | ], 27 | ], 28 | 'class_definition' => [ 29 | 'multi_line_extends_each_single_line' => true, 30 | 'single_item_single_line' => true, 31 | 'single_line' => true, 32 | ], 33 | 'concat_space' => [ 34 | 'spacing' => 'none', 35 | ], 36 | 'constant_case' => ['case' => 'lower'], 37 | 'declare_equal_normalize' => true, 38 | 'elseif' => true, 39 | 'encoding' => true, 40 | 'full_opening_tag' => true, 41 | 'fully_qualified_strict_types' => false, 42 | // added by Shift 43 | 'function_declaration' => true, 44 | 'function_typehint_space' => true, 45 | 'general_phpdoc_tag_rename' => true, 46 | 'heredoc_to_nowdoc' => true, 47 | 'include' => true, 48 | 'increment_style' => ['style' => 'post'], 49 | 'indentation_type' => true, 50 | 'linebreak_after_opening_tag' => true, 51 | 'line_ending' => true, 52 | 'lowercase_cast' => true, 53 | 'lowercase_keywords' => true, 54 | 'lowercase_static_reference' => true, 55 | // added from Symfony 56 | 'magic_method_casing' => true, 57 | // added from Symfony 58 | 'magic_constant_casing' => true, 59 | 'method_argument_space' => [ 60 | 'on_multiline' => 'ignore', 61 | ], 62 | 'multiline_whitespace_before_semicolons' => [ 63 | 'strategy' => 'no_multi_line', 64 | ], 65 | 'native_function_casing' => true, 66 | 'no_alias_functions' => true, 67 | 'no_extra_blank_lines' => [ 68 | 'tokens' => [ 69 | 'extra', 70 | 'throw', 71 | 'use', 72 | 'switch', 73 | 'case', 74 | 'default', 75 | ], 76 | ], 77 | 'no_blank_lines_after_class_opening' => true, 78 | 'no_blank_lines_after_phpdoc' => true, 79 | 'no_closing_tag' => true, 80 | 'no_empty_phpdoc' => true, 81 | 'no_empty_statement' => true, 82 | 'no_leading_import_slash' => true, 83 | 'no_leading_namespace_whitespace' => true, 84 | 'no_mixed_echo_print' => [ 85 | 'use' => 'echo', 86 | ], 87 | 'no_multiline_whitespace_around_double_arrow' => true, 88 | 'no_short_bool_cast' => true, 89 | 'no_singleline_whitespace_before_semicolons' => true, 90 | 'no_spaces_after_function_name' => true, 91 | 'no_spaces_around_offset' => [ 92 | 'positions' => [ 93 | 'inside', 94 | 'outside', 95 | ], 96 | ], 97 | 'no_spaces_inside_parenthesis' => true, 98 | 'no_trailing_comma_in_list_call' => true, 99 | 'no_trailing_comma_in_singleline_array' => true, 100 | 'no_trailing_whitespace' => true, 101 | 'no_trailing_whitespace_in_comment' => true, 102 | 'no_unneeded_control_parentheses' => [ 103 | 'statements' => [ 104 | 'break', 105 | 'clone', 106 | 'continue', 107 | 'echo_print', 108 | 'return', 109 | 'switch_case', 110 | 'yield', 111 | ], 112 | ], 113 | 'no_unreachable_default_argument_value' => true, 114 | 'no_useless_return' => true, 115 | 'no_whitespace_before_comma_in_array' => true, 116 | 'no_whitespace_in_blank_line' => true, 117 | 'normalize_index_brace' => true, 118 | 'not_operator_with_successor_space' => true, 119 | 'object_operator_without_whitespace' => true, 120 | 'ordered_imports' => [ 121 | 'sort_algorithm' => 'alpha', 122 | 'imports_order' => [ 123 | 'class', 124 | 'function', 125 | 'const', 126 | ], 127 | ], 128 | 'psr_autoloading' => true, 129 | 'phpdoc_indent' => true, 130 | 'phpdoc_inline_tag_normalizer' => true, 131 | 'phpdoc_no_access' => true, 132 | 'phpdoc_no_package' => true, 133 | 'phpdoc_no_useless_inheritdoc' => true, 134 | 'phpdoc_scalar' => true, 135 | 'phpdoc_single_line_var_spacing' => true, 136 | 'phpdoc_summary' => false, 137 | 'phpdoc_to_comment' => false, 138 | // override to preserve user preference 139 | 'phpdoc_tag_type' => true, 140 | 'phpdoc_trim' => true, 141 | 'phpdoc_types' => true, 142 | 'phpdoc_var_without_name' => true, 143 | 'self_accessor' => true, 144 | 'short_scalar_cast' => true, 145 | 'simplified_null_return' => false, 146 | // disabled as "risky" 147 | 'single_blank_line_at_eof' => true, 148 | 'single_blank_line_before_namespace' => true, 149 | 'single_class_element_per_statement' => [ 150 | 'elements' => [ 151 | 'const', 152 | 'property', 153 | ], 154 | ], 155 | 'single_import_per_statement' => true, 156 | 'single_line_after_imports' => true, 157 | 'single_line_comment_style' => [ 158 | 'comment_types' => ['hash'], 159 | ], 160 | 'single_quote' => true, 161 | 'space_after_semicolon' => true, 162 | 'standardize_not_equals' => true, 163 | 'switch_case_semicolon_to_colon' => true, 164 | 'switch_case_space' => true, 165 | 'ternary_operator_spaces' => true, 166 | 'trailing_comma_in_multiline' => [ 167 | 'elements' => [ 168 | 'arrays', 169 | 'parameters', 170 | ], 171 | ], 172 | 'trim_array_spaces' => true, 173 | 'types_spaces' => [ 174 | 'space' => 'single', 175 | ], 176 | 'unary_operator_spaces' => true, 177 | 'visibility_required' => [ 178 | 'elements' => [ 179 | 'method', 180 | 'property', 181 | 'const', 182 | ], 183 | ], 184 | 'whitespace_after_comma_in_array' => true, 185 | 186 | // DCC 187 | 'align_multiline_comment' => ['comment_type' => 'phpdocs_like'], 188 | 'simplified_if_return' => true, 189 | 'method_chaining_indentation' => true, 190 | ]; 191 | 192 | $finder = Finder::create() 193 | ->in([ 194 | __DIR__ . '/src', 195 | __DIR__ . '/tests', 196 | ]) 197 | ->name('*.php') 198 | ->notName('*.blade.php') 199 | ->ignoreDotFiles(true) 200 | ->ignoreVCS(true); 201 | 202 | return (new Config) 203 | ->setFinder($finder) 204 | ->setRules($rules) 205 | ->setRiskyAllowed(true) 206 | ->setUsingCache(true); 207 | -------------------------------------------------------------------------------- /src/Traits/Callbacks.php: -------------------------------------------------------------------------------- 1 | createUserUsing = $callback; 41 | 42 | return $this; 43 | } 44 | 45 | /** 46 | * @return \Closure(string $provider, \Laravel\Socialite\Contracts\User $oauthUser, \DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin): \Illuminate\Contracts\Auth\Authenticatable 47 | */ 48 | public function getCreateUserUsing(): Closure 49 | { 50 | return $this->createUserUsing ?? function ( 51 | string $provider, 52 | SocialiteUserContract $oauthUser, 53 | FilamentSocialitePlugin $plugin, 54 | ) { 55 | /** 56 | * @var \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model&\Illuminate\Contracts\Auth\Authenticatable> $query 57 | */ 58 | $query = (new $this->userModelClass())->query(); 59 | 60 | return $query->create([ 61 | 'name' => $oauthUser->getName(), 62 | 'email' => $oauthUser->getEmail(), 63 | ]); 64 | }; 65 | } 66 | 67 | /** 68 | * @param \Closure(string $provider, \DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser $socialiteUser, \DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin): \Illuminate\Http\RedirectResponse $callback 69 | */ 70 | public function redirectAfterLoginUsing(Closure $callback): static 71 | { 72 | $this->redirectAfterLoginUsing = $callback; 73 | 74 | return $this; 75 | } 76 | 77 | /** 78 | * @return \Closure(string $provider, \DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser $socialiteUser, \DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin): \Symfony\Component\HttpFoundation\Response 79 | */ 80 | public function getRedirectAfterLoginUsing(): Closure 81 | { 82 | return $this->redirectAfterLoginUsing ?? function (string $provider, FilamentSocialiteUserContract $socialiteUser, FilamentSocialitePlugin $plugin) { 83 | if (($panel = $this->getPanel())->hasTenancy()) { 84 | $tenant = Filament::getUserDefaultTenant($socialiteUser->getUser()); 85 | 86 | if (is_null($tenant) && $tenantRegistrationUrl = $panel->getTenantRegistrationUrl()) { 87 | return redirect()->intended($tenantRegistrationUrl); 88 | } 89 | 90 | return redirect()->intended( 91 | $panel->getUrl($tenant) 92 | ); 93 | } 94 | 95 | return redirect()->intended( 96 | $this->getPanel()->getUrl() 97 | ); 98 | }; 99 | } 100 | 101 | /** 102 | * @param ?\Closure(string $provider, \Laravel\Socialite\Contracts\User $oauthUser, \DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin): ?(\Illuminate\Contracts\Auth\Authenticatable) $callback 103 | */ 104 | public function resolveUserUsing(?Closure $callback = null): static 105 | { 106 | $this->resolveUserUsing = $callback; 107 | 108 | return $this; 109 | } 110 | 111 | /** 112 | * @return \Closure(string $provider, \Laravel\Socialite\Contracts\User $oauthUser, \DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin): ?(\Illuminate\Contracts\Auth\Authenticatable) 113 | */ 114 | public function getResolveUserUsing(): Closure 115 | { 116 | return $this->resolveUserUsing ?? function (string $provider, SocialiteUserContract $oauthUser, FilamentSocialitePlugin $plugin) { 117 | /** @var \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model&\Illuminate\Contracts\Auth\Authenticatable> $model */ 118 | $model = (new $this->userModelClass()); 119 | 120 | return $model->where( 121 | 'email', 122 | $oauthUser->getEmail() 123 | )->first(); 124 | }; 125 | } 126 | 127 | /** 128 | * @param ?\Closure(\DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin, \Laravel\Socialite\Contracts\User $oauthUser): bool $callback 129 | */ 130 | public function authorizeUserUsing(?Closure $callback = null): static 131 | { 132 | $this->authorizeUserUsing = $callback; 133 | 134 | return $this; 135 | } 136 | 137 | /** 138 | * @return \Closure(\DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin, \Laravel\Socialite\Contracts\User $oauthUser): bool 139 | */ 140 | public function getAuthorizeUserUsing(): Closure 141 | { 142 | return $this->authorizeUserUsing ?? static::checkDomainAllowList(...); 143 | } 144 | 145 | public static function checkDomainAllowList(FilamentSocialitePlugin $plugin, SocialiteUserContract $oauthUser): bool 146 | { 147 | $domains = $plugin->getDomainAllowList(); 148 | 149 | // When no domains are specified, all users are allowed 150 | if (count($domains) < 1) { 151 | return true; 152 | } 153 | 154 | // Get the domain of the email for the specified user 155 | $emailDomain = Str::of($oauthUser->getEmail() ?? throw new ImplementationException('User email is required.')) 156 | ->afterLast('@') 157 | ->lower() 158 | ->__toString(); 159 | 160 | // See if everything after @ is in the domains array 161 | return in_array($emailDomain, $domains); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/Http/Controllers/SocialiteLoginController.php: -------------------------------------------------------------------------------- 1 | plugin()->isProviderConfigured($provider)) { 29 | throw ProviderNotConfigured::make($provider); 30 | } 31 | 32 | /** @var \Laravel\Socialite\Two\AbstractProvider $driver */ 33 | $driver = Socialite::driver($provider); 34 | 35 | $response = $driver 36 | ->with([ 37 | ...$this->plugin()->getProvider($provider)->getWith(), 38 | 'state' => $state = PanelFromUrlQuery::encrypt($this->plugin()->getPanel()->getId()), 39 | ]) 40 | ->scopes($this->plugin()->getProvider($provider)->getScopes()) 41 | ->redirect(); 42 | 43 | // Set state value to be equal to the encrypted panel id. This value is used to 44 | // retrieve the panel id once the authentication returns to our application, 45 | // and it still prevents CSRF as it is non-guessable value. 46 | session()->put('state', $state); 47 | 48 | return $response; 49 | } 50 | 51 | protected function retrieveOauthUser(string $provider): ?SocialiteUserContract 52 | { 53 | $stateless = $this->plugin()->getProvider($provider)->getStateless(); 54 | 55 | try { 56 | /** @var \Laravel\Socialite\Two\AbstractProvider $driver */ 57 | $driver = Socialite::driver($provider); 58 | 59 | return $stateless 60 | ? $driver->stateless()->user() 61 | : $driver->user(); 62 | } catch (InvalidStateException $e) { 63 | Events\InvalidState::dispatch($e); 64 | } 65 | 66 | return null; 67 | } 68 | 69 | protected function retrieveSocialiteUser(string $provider, SocialiteUserContract $oauthUser): ?FilamentSocialiteUserContract 70 | { 71 | return $this->plugin()->getSocialiteUserModel()::findForProvider($provider, $oauthUser); 72 | } 73 | 74 | protected function redirectToLogin(string $message): RedirectResponse 75 | { 76 | // Add error message to the session, this way we can show an error message on the form. 77 | session()->flash('filament-socialite-login-error', __($message)); 78 | 79 | return redirect()->route($this->plugin()->getLoginRouteName()); 80 | } 81 | 82 | protected function authorizeUser(SocialiteUserContract $oauthUser): bool 83 | { 84 | return app()->call($this->plugin()->getAuthorizeUserUsing(), ['plugin' => $this->plugin(), 'oauthUser' => $oauthUser]); 85 | } 86 | 87 | protected function loginUser(string $provider, FilamentSocialiteUserContract $socialiteUser, SocialiteUserContract $oauthUser): Response 88 | { 89 | // Log the user in 90 | $this->plugin()->getGuard()->login($socialiteUser->getUser(), $this->plugin()->getRememberLogin()); 91 | 92 | // Dispatch the login event 93 | Events\Login::dispatch($socialiteUser, $oauthUser); 94 | 95 | return app()->call($this->plugin()->getRedirectAfterLoginUsing(), ['provider' => $provider, 'socialiteUser' => $socialiteUser, 'plugin' => $this->plugin]); 96 | } 97 | 98 | protected function registerSocialiteUser(string $provider, SocialiteUserContract $oauthUser, Authenticatable $user): Response 99 | { 100 | // Create a socialite user 101 | $socialiteUser = $this->plugin()->getSocialiteUserModel()::createForProvider($provider, $oauthUser, $user); 102 | 103 | // Dispatch the socialite user connected event 104 | Events\SocialiteUserConnected::dispatch($provider, $oauthUser, $socialiteUser); 105 | 106 | // Login the user 107 | return $this->loginUser($provider, $socialiteUser, $oauthUser); 108 | } 109 | 110 | protected function registerOauthUser(string $provider, SocialiteUserContract $oauthUser): Response 111 | { 112 | $socialiteUser = DB::transaction(function () use ($provider, $oauthUser) { 113 | // Create a user 114 | $user = app()->call($this->plugin()->getCreateUserUsing(), ['provider' => $provider, 'oauthUser' => $oauthUser, 'plugin' => $this->plugin]); 115 | 116 | // Create a socialite user 117 | return $this->plugin()->getSocialiteUserModel()::createForProvider($provider, $oauthUser, $user); 118 | }); 119 | 120 | // Dispatch the registered event 121 | Events\Registered::dispatch($provider, $oauthUser, $socialiteUser); 122 | 123 | // Login the user 124 | return $this->loginUser($provider, $socialiteUser, $oauthUser); 125 | } 126 | 127 | public function processCallback(string $provider): Response 128 | { 129 | if (! $this->plugin()->isProviderConfigured($provider)) { 130 | throw ProviderNotConfigured::make($provider); 131 | } 132 | 133 | // Try to retrieve existing user 134 | $oauthUser = $this->retrieveOauthUser($provider); 135 | 136 | if (is_null($oauthUser)) { 137 | return $this->redirectToLogin('filament-socialite::auth.login-failed'); 138 | } 139 | 140 | // Verify if the user is authorized. 141 | if (! $this->authorizeUser($oauthUser)) { 142 | Events\UserNotAllowed::dispatch($oauthUser); 143 | 144 | return $this->redirectToLogin('filament-socialite::auth.user-not-allowed'); 145 | } 146 | 147 | // Try to find a socialite user 148 | $socialiteUser = $this->retrieveSocialiteUser($provider, $oauthUser); 149 | if ($socialiteUser) { 150 | return $this->loginUser($provider, $socialiteUser, $oauthUser); 151 | } 152 | 153 | // See if a user already exists, but not for this socialite provider 154 | $user = app()->call($this->plugin()->getResolveUserUsing(), [ 155 | 'provider' => $provider, 156 | 'oauthUser' => $oauthUser, 157 | 'plugin' => $this->plugin, 158 | ]); 159 | 160 | // See if registration is allowed 161 | if (! $this->evaluate($this->plugin()->getRegistration(), ['provider' => $provider, 'oauthUser' => $oauthUser, 'user' => $user])) { 162 | Events\RegistrationNotEnabled::dispatch($provider, $oauthUser, $user); 163 | 164 | return $this->redirectToLogin('filament-socialite::auth.registration-not-enabled'); 165 | } 166 | 167 | // Handle registration 168 | return $user 169 | ? $this->registerSocialiteUser($provider, $oauthUser, $user) 170 | : $this->registerOauthUser($provider, $oauthUser); 171 | } 172 | 173 | protected function plugin(): FilamentSocialitePlugin 174 | { 175 | return $this->plugin ??= FilamentSocialitePlugin::current(); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `filament-socialite` will be documented in this file. 4 | 5 | ## [3.0.1 - 2025-12-16](https://github.com/DutchCodingCompany/filament-socialite/compare/3.0.0...3.0.1) 6 | 7 | ## What's Changed 8 | - Bump actions/checkout from 4 to 5 by @dependabot[bot] in https://github.com/DutchCodingCompany/filament-socialite/pull/135 9 | - Bump stefanzweifel/git-auto-commit-action from 6 to 7 by @dependabot[bot] in https://github.com/DutchCodingCompany/filament-socialite/pull/139 10 | - Bump actions/checkout from 5 to 6 by @dependabot[bot] in https://github.com/DutchCodingCompany/filament-socialite/pull/141 11 | - Add foreign key constraint with cascade behaviour. by @chrillep in https://github.com/DutchCodingCompany/filament-socialite/pull/143 12 | - **NOTE**: this will not change existing `socialite_users` tables. Please **consider adding** the constraint in a separate migration yourself 13 | 14 | ## [3.0.0 - 2025-08-11](https://github.com/DutchCodingCompany/filament-socialite/compare/2.4.0...3.0.0) 15 | - Tag major version 16 | - BREAKING CHANGE: Implement fix for slug issue https://github.com/DutchCodingCompany/filament-socialite/issues/127 17 | The package now uses `path` instead of `id` as default prefix as it should have done. In order to revert to previous behaviour, use slug to override the behaviour: 18 | ```php 19 | ->plugin( 20 | FilamentSocialitePlugin::make() 21 | ->slug('admin') // change this to the panel's ID 22 | // other config for plugin 23 | ) 24 | ``` 25 | 26 | ## [3.0.0-beta3 - 2025-07-18](https://github.com/DutchCodingCompany/filament-socialite/compare/3.0.0-beta2...3.0.0-beta3) 27 | 28 | ## What's Changed 29 | * Include compiled styles 30 | 31 | ## [3.0.0-beta2 - 2025-06-23](https://github.com/DutchCodingCompany/filament-socialite/compare/3.0.0-alpha1...3.0.0-beta2) 32 | 33 | ## What's Changed 34 | * BREAKING CHANGE: Implement fix for slug issue https://github.com/DutchCodingCompany/filament-socialite/issues/127 35 | The package now uses `path` instead of `id` as default prefix as it should have done. In order to revert to previous behaviour, use slug to override the behaviour: 36 | ```php 37 | ->plugin( 38 | FilamentSocialitePlugin::make() 39 | ->slug('admin') // change this to the panel's ID 40 | // other config for plugin 41 | ) 42 | ``` 43 | 44 | ## [3.0.0-alpha1 - 2025-06-05](https://github.com/DutchCodingCompany/filament-socialite/compare/2.4.0...3.0.0-alpha1) / [3.0.0-beta1 - 2025-06-05](https://github.com/DutchCodingCompany/filament-socialite/compare/2.4.0...3.0.0-beta1) 45 | 46 | ## What's Changed 47 | * Filament V4 support by @erikgaal in https://github.com/DutchCodingCompany/filament-socialite/pull/131 48 | 49 | ## [2.4.0 - 2025-02-25](https://github.com/DutchCodingCompany/filament-socialite/compare/2.3.1...2.4.0) 50 | 51 | ## What's Changed 52 | * Laravel 12.x Compatibility by @laravel-shift in https://github.com/DutchCodingCompany/filament-socialite/pull/125 53 | 54 | ## [2.3.1 - 2025-02-06](https://github.com/DutchCodingCompany/filament-socialite/compare/2.3.0...2.3.1) 55 | 56 | ## What's Changed 57 | * Bump dependabot/fetch-metadata from 2.2.0 to 2.3.0 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/123 58 | * Add data to events by @dododedodonl in https://github.com/DutchCodingCompany/filament-socialite/pull/124 59 | 60 | ## [2.3.0 - 2024-11-29](https://github.com/DutchCodingCompany/filament-socialite/compare/2.2.1...2.3.0) 61 | 62 | ## What's Changed 63 | * Add option to hide providers by @dododedodonl in https://github.com/DutchCodingCompany/filament-socialite/pull/122 64 | * Add support for php 8.4 65 | 66 | ## [2.2.1 - 2024-07-17](https://github.com/DutchCodingCompany/filament-socialite/compare/2.2.0...2.2.1) 67 | 68 | ## What's Changed 69 | * Revert model property changes by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/110 70 | 71 | ## [2.2.0 - 2024-07-15](https://github.com/DutchCodingCompany/filament-socialite/compare/2.1.1...2.2.0) 72 | 73 | ## What's Changed 74 | * Bump dependabot/fetch-metadata from 2.1.0 to 2.2.0 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/106 75 | * Add new callback route for stateless OAuth flows by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/105 76 | 77 | ## [2.1.1 - 2024-06-21](https://github.com/DutchCodingCompany/filament-socialite/compare/2.1.0...2.1.1) 78 | 79 | ## What's Changed 80 | * Improve Socialite driver typings + callable typings by @juliangums in https://github.com/DutchCodingCompany/filament-socialite/pull/103 81 | 82 | ## New Contributors 83 | * @juliangums made their first contribution in https://github.com/DutchCodingCompany/filament-socialite/pull/103 84 | 85 | ## [2.1.0 - 2024-06-21](https://github.com/DutchCodingCompany/filament-socialite/compare/2.0.0...2.1.0) 86 | 87 | * Add Authorization Callback by @petecoop in https://github.com/DutchCodingCompany/filament-socialite/pull/100 88 | 89 | ## [2.0.0 - 2024-06-04](https://github.com/DutchCodingCompany/filament-socialite/compare/1.5.0...2.0.0) 90 | * **Please check the revised [README.md](https://github.com/DutchCodingCompany/filament-socialite/blob/main/README.md) and [UPGRADE.md](https://github.com/DutchCodingCompany/filament-socialite/blob/main/UPGRADE.md)! Many functions have been renamed.** 91 | * Refactor package for better consistency with Filament code standards https://github.com/DutchCodingCompany/filament-socialite/pull/90 92 | 93 | ## [1.5.0 - 2024-06-04](https://github.com/DutchCodingCompany/filament-socialite/compare/1.4.1...1.5.0) 94 | 95 | * Bump dependabot/fetch-metadata from 1.6.0 to 2.0.0 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/89 96 | * Bump dependabot/fetch-metadata from 2.0.0 to 2.1.0 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/91 97 | * Compatible with Stateless Authentication by @LittleHans8 in https://github.com/DutchCodingCompany/filament-socialite/pull/96 98 | 99 | ## [1.4.1 - 2024-03-20](https://github.com/DutchCodingCompany/filament-socialite/compare/v1.4.0...1.4.1) 100 | * Provide oauth user to login event by @dcc-bjorn in https://github.com/DutchCodingCompany/filament-socialite/pull/88 101 | 102 | ## [1.4.0 - 2024-03-12](https://github.com/DutchCodingCompany/filament-socialite/compare/v1.3.1...1.4.0) 103 | * Laravel 11 support by @dododedodonl in https://github.com/DutchCodingCompany/filament-socialite/pull/87 104 | 105 | ## [1.3.1 - 2024-03-05](https://github.com/DutchCodingCompany/filament-socialite/compare/v1.3.0...1.3.1) 106 | * Add $provider as required by the callback by @phh in https://github.com/DutchCodingCompany/filament-socialite/pull/83 107 | * Never use SPA mode for oauth links + spacing by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/85 108 | 109 | ## [1.3.0 - 2024-03-01](https://github.com/DutchCodingCompany/filament-socialite/compare/v1.2.0...1.3.0) 110 | * Update CHANGELOG.md by @bramr94 in https://github.com/DutchCodingCompany/filament-socialite/pull/70 111 | * Add socialite test by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/78 112 | * Improve actions by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/79 113 | * Bump stefanzweifel/git-auto-commit-action from 4 to 5 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/51 114 | * feature: allow socialite user model customization by @kykurniawan in https://github.com/DutchCodingCompany/filament-socialite/pull/72 115 | * Add registration enabled callable by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/80 116 | * Multi-tenancy support by @bramr94 in https://github.com/DutchCodingCompany/filament-socialite/pull/76 117 | 118 | ## [1.2.0 - 2024-01-31](https://github.com/DutchCodingCompany/filament-socialite/compare/v1.1.1...1.2.0) 119 | - Add option to add optional parameters in https://github.com/DutchCodingCompany/filament-socialite/pull/69 120 | 121 | ## [1.1.1 - 2024-01-18](https://github.com/DutchCodingCompany/filament-socialite/compare/1.1.0...1.1.1) 122 | - Improve domain routing in https://github.com/DutchCodingCompany/filament-socialite/pull/61 123 | - Update README in https://github.com/DutchCodingCompany/filament-socialite/pull/64 124 | 125 | ## [1.1.0 - 2024-01-08](https://github.com/DutchCodingCompany/filament-socialite/compare/1.0.1...1.1.0) 126 | - Add button customization options in https://github.com/DutchCodingCompany/filament-socialite/pull/59 127 | 128 | ## [1.0.1 - 2023-12-18](https://github.com/DutchCodingCompany/filament-socialite/compare/1.0.0...1.0.1) 129 | - Resolve plugin registration issue [#54](https://github.com/DutchCodingCompany/filament-socialite/issues/54) 130 | 131 | ## [1.0.0 - 2023-12-05](https://github.com/DutchCodingCompany/filament-socialite/compare/0.2.2...1.0.0) 132 | - Added support for Filament v3 through the plugin setup 133 | - Added support for multiple panels 134 | - See [UPGRADE.md](UPGRADE.md) 135 | 136 | ## 0.2.2 - 2022-06-14 137 | 138 | ### What's Changed 139 | 140 | - Fix readme by @dododedodonl in https://github.com/DutchCodingCompany/filament-socialite/pull/15 141 | - use Filament-fortify render hook by @wychoong in https://github.com/DutchCodingCompany/filament-socialite/pull/16 142 | 143 | ### New Contributors 144 | 145 | - @wychoong made their first contribution in https://github.com/DutchCodingCompany/filament-socialite/pull/16 146 | 147 | **Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.2.1...0.2.2 148 | 149 | ## 0.2.1 - 2022-05-25 150 | 151 | ## What's Changed 152 | 153 | - Fix user model instantiating by @marcoboers in https://github.com/DutchCodingCompany/filament-socialite/pull/14 154 | 155 | **Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.2.0...0.2.1 156 | 157 | ## 0.2.0 - 2022-05-24 158 | 159 | ## Breaking changes 160 | 161 | - `Events\DomainFailed` renamed to `Events\UserNotAllowed` 162 | - `Events\RegistrationFailed` renamed to `Events\RegistrationNotEnabled` 163 | 164 | ## What's Changed 165 | 166 | - Refactor the controller for extendability and customization by @dododedodonl in https://github.com/DutchCodingCompany/filament-socialite/pull/13 167 | 168 | ## New Contributors 169 | 170 | - @dododedodonl made their first contribution in https://github.com/DutchCodingCompany/filament-socialite/pull/13 171 | 172 | **Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.1.5...0.2.0 173 | 174 | ## 0.1.5 - 2022-05-20 175 | 176 | ## What's Changed 177 | 178 | - Fix missing variable for registered event by @marcoboers in https://github.com/DutchCodingCompany/filament-socialite/pull/11 179 | 180 | **Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.1.4...0.1.5 181 | 182 | ## 0.1.4 - 2022-05-06 183 | 184 | ## What's Changed 185 | 186 | - Feature: Adds buttons blade component by @oyepez003 in https://github.com/DutchCodingCompany/filament-socialite/pull/8 187 | - Feature: Add login events dispatching by @marcoboers in https://github.com/DutchCodingCompany/filament-socialite/pull/5 188 | 189 | **Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.1.3...0.1.4 190 | 191 | ## 0.1.3 - 2022-05-04 192 | 193 | ## What's Changed 194 | 195 | - Bugfix: Avoid returning 403 when a user exists based on the oauth-email . by @oyepez003 in https://github.com/DutchCodingCompany/filament-socialite/pull/7 196 | 197 | ## New Contributors 198 | 199 | - @oyepez003 made their first contribution in https://github.com/DutchCodingCompany/filament-socialite/pull/7 200 | 201 | **Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.1.2...0.1.3 202 | 203 | ## 0.1.2 - 2022-05-03 204 | 205 | ## What's Changed 206 | 207 | - Bump dependabot/fetch-metadata from 1.3.0 to 1.3.1 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/2 208 | - Add Laravel 8 support and make fontawesome icons optional by @marcoboers in https://github.com/DutchCodingCompany/filament-socialite/pull/4 209 | 210 | ## New Contributors 211 | 212 | - @marcoboers made their first contribution in https://github.com/DutchCodingCompany/filament-socialite/pull/4 213 | 214 | **Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.1.1...0.1.2 215 | 216 | ## 0.1.1 - 2022-04-11 217 | 218 | ## What's Changed 219 | 220 | - Fix registration flow 221 | 222 | ## 0.1.0 - 2022-04-08 223 | 224 | ### Initial Release 225 | 226 | - Add social login links to login page 227 | - Support Socialite OAuth flow 228 | - Support registration flow 229 | - Support domain allowlist for internal use 230 | - Dark mode support 231 | - Blade Font Awesome brand icons 232 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | # Social login for Filament through Laravel Socialite 7 | 8 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/dutchcodingcompany/filament-socialite.svg?style=flat-square)](https://packagist.org/packages/dutchcodingcompany/filament-socialite) 9 | [![GitHub Tests Action Status](https://img.shields.io/github/workflow/status/dutchcodingcompany/filament-socialite/run-tests?label=tests)](https://github.com/dutchcodingcompany/filament-socialite/actions?query=workflow%3Arun-tests+branch%3Amain) 10 | [![GitHub Code Style Action Status](https://img.shields.io/github/workflow/status/dutchcodingcompany/filament-socialite/Check%20&%20fix%20styling?label=code%20style)](https://github.com/dutchcodingcompany/filament-socialite/actions?query=workflow%3A"Check+%26+fix+styling"+branch%3Amain) 11 | [![Total Downloads](https://img.shields.io/packagist/dt/dutchcodingcompany/filament-socialite.svg?style=flat-square)](https://packagist.org/packages/dutchcodingcompany/filament-socialite) 12 | 13 | Add OAuth2 login through Laravel Socialite to Filament. OAuth1 (eg. Twitter) is not supported at this time. 14 | 15 | ## Installation 16 | 17 | | Filament version | Package version | Readme | 18 | |----------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------|--------------------------------------------------------------------------------------| 19 | | [^4.0.0](https://github.com/filamentphp/filament/tree/4.x) | 3.x.x | [Link](https://github.com/DutchCodingCompany/filament-socialite/blob/main/README.md) | 20 | | [^3.2.44](https://github.com/filamentphp/filament/releases/tag/v3.2.44) (if using [SPA mode](https://filamentphp.com/docs/3.x/panels/configuration#spa-mode)) | 2.x.x | [Link](https://github.com/DutchCodingCompany/filament-socialite/blob/2.x/README.md) | 21 | | [^3.2.44](https://github.com/filamentphp/filament/releases/tag/v3.2.44) (if using [SPA mode](https://filamentphp.com/docs/3.x/panels/configuration#spa-mode)) | ^1.3.1 | | 22 | | 3.x | 1.x.x | [Link](https://github.com/DutchCodingCompany/filament-socialite/blob/1.x/README.md) | 23 | | 2.x | 0.x.x | | 24 | 25 | Install the package via composer: 26 | 27 | ```bash 28 | composer require dutchcodingcompany/filament-socialite 29 | ``` 30 | 31 | Publish and migrate the migration file: 32 | 33 | ```bash 34 | php artisan vendor:publish --tag="filament-socialite-migrations" 35 | php artisan migrate 36 | ``` 37 | 38 | Other configuration files include: 39 | ```bash 40 | php artisan vendor:publish --tag="filament-socialite-config" 41 | php artisan vendor:publish --tag="filament-socialite-views" 42 | php artisan vendor:publish --tag="filament-socialite-translations" 43 | ``` 44 | 45 | You need to register the plugin in the Filament panel provider (the default filename is `app/Providers/Filament/AdminPanelProvider.php`). The following options are available: 46 | 47 | ```php 48 | use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin; 49 | use DutchCodingCompany\FilamentSocialite\Provider; 50 | use Filament\Support\Colors; 51 | use Laravel\Socialite\Contracts\User as SocialiteUserContract; 52 | use Illuminate\Contracts\Auth\Authenticatable; 53 | 54 | // ... 55 | ->plugin( 56 | FilamentSocialitePlugin::make() 57 | // (required) Add providers corresponding with providers in `config/services.php`. 58 | ->providers([ 59 | // Create a provider 'gitlab' corresponding to the Socialite driver with the same name. 60 | Provider::make('gitlab') 61 | ->label('GitLab') 62 | ->icon('fab-gitlab') 63 | ->color(Color::hex('#2f2a6b')) 64 | ->outlined(false) 65 | ->stateless(false) 66 | ->scopes(['...']) 67 | ->with(['...']), 68 | ]) 69 | // (optional) Override the panel slug to be used in the oauth routes. Defaults to the panel's configured path. 70 | ->slug('admin') 71 | // (optional) Enable/disable registration of new (socialite-) users. 72 | ->registration(true) 73 | // (optional) Enable/disable registration of new (socialite-) users using a callback. 74 | // In this example, a login flow can only continue if there exists a user (Authenticatable) already. 75 | ->registration(fn (string $provider, SocialiteUserContract $oauthUser, ?Authenticatable $user) => (bool) $user) 76 | // (optional) Change the associated model class. 77 | ->userModelClass(\App\Models\User::class) 78 | // (optional) Change the associated socialite class (see below). 79 | ->socialiteUserModelClass(\App\Models\SocialiteUser::class) 80 | ); 81 | ``` 82 | 83 | This package automatically adds 2 routes per panel to make the OAuth flow possible: a redirector and a callback. When 84 | setting up your **external OAuth app configuration**, enter the following callback URL (in this case for the Filament 85 | panel with ID `admin` and the `github` provider): 86 | ``` 87 | https://example.com/admin/oauth/callback/github 88 | ``` 89 | 90 | A multi-panel callback route is available as well that does not contain the panel ID in the url. Instead, it determines 91 | the panel ID from an encrypted `state` input (`...?state=abcd1234`). This allows you to create a single OAuth 92 | application for multiple Filament panels that use the same callback URL. Note that this only works for _stateful_ OAuth 93 | apps: 94 | 95 | ``` 96 | https://example.com/oauth/callback/github 97 | ``` 98 | 99 | If in doubt, run `php artisan route:list` to see which routes are available to you. 100 | 101 | ### Icons 102 | 103 | You can specify a custom icon for each of your login providers. You can add Font Awesome brand 104 | icons made available through [Blade Font Awesome](https://github.com/owenvoke/blade-fontawesome) by running: 105 | ```bash 106 | composer require owenvoke/blade-fontawesome 107 | ``` 108 | 109 | ### Registration flow 110 | 111 | This package supports account creation for users. However, to support this flow it is important that the `password` 112 | attribute on your `User` model is nullable. For example, by adding the following to your users table migration. 113 | Or you could opt for customizing the user creation, see below. 114 | 115 | ```php 116 | $table->string('password')->nullable(); 117 | ``` 118 | 119 | ### Domain Allow list 120 | 121 | This package supports the option to limit the users that can login with the OAuth login to users of a certain domain. 122 | This can be used to setup SSO for internal use. 123 | 124 | ```php 125 | ->plugin( 126 | FilamentSocialitePlugin::make() 127 | // ... 128 | ->registration(true) 129 | ->domainAllowList(['localhost']) 130 | ); 131 | ``` 132 | 133 | ### Changing how an Authenticatable user is created or retrieved 134 | 135 | You can use the `createUserUsing` and `resolveUserUsing` methods to change how a user is created or retrieved. 136 | 137 | ```php 138 | use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin; 139 | use Laravel\Socialite\Contracts\User as SocialiteUserContract; 140 | 141 | ->plugin( 142 | FilamentSocialitePlugin::make() 143 | // ... 144 | ->createUserUsing(function (string $provider, SocialiteUserContract $oauthUser, FilamentSocialitePlugin $plugin) { 145 | // Logic to create a new user. 146 | }) 147 | ->resolveUserUsing(function (string $provider, SocialiteUserContract $oauthUser, FilamentSocialitePlugin $plugin) { 148 | // Logic to retrieve an existing user. 149 | }) 150 | ... 151 | ); 152 | ``` 153 | 154 | ### Change how a Socialite user is created or retrieved 155 | 156 | In your plugin options in your Filament panel, add the following method: 157 | 158 | ```php 159 | // app/Providers/Filament/AdminPanelProvider.php 160 | ->plugins([ 161 | FilamentSocialitePlugin::make() 162 | // ... 163 | ->socialiteUserModelClass(\App\Models\SocialiteUser::class) 164 | ``` 165 | 166 | This class should at the minimum implement the [`FilamentSocialiteUser`](/src/Models/Contracts/FilamentSocialiteUser.php) interface, like so: 167 | 168 | ```php 169 | namespace App\Models; 170 | 171 | use DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser as FilamentSocialiteUserContract; 172 | use Illuminate\Contracts\Auth\Authenticatable; 173 | use Laravel\Socialite\Contracts\User as SocialiteUserContract; 174 | 175 | class SocialiteUser implements FilamentSocialiteUserContract 176 | { 177 | public function getUser(): Authenticatable 178 | { 179 | // 180 | } 181 | 182 | public static function findForProvider(string $provider, SocialiteUserContract $oauthUser): ?self 183 | { 184 | // 185 | } 186 | 187 | public static function createForProvider( 188 | string $provider, 189 | SocialiteUserContract $oauthUser, 190 | Authenticatable $user 191 | ): self { 192 | // 193 | } 194 | } 195 | ``` 196 | 197 | ### Check if the user is authorized to use the application 198 | 199 | You can use the `authorizeUserUsing` method to check if the user is authorized to use the application. **Note:** by [default](/src/Traits/Callbacks.php#L145) this method check if the user's email domain is in the domain allow list. 200 | 201 | ```php 202 | use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin; 203 | use Laravel\Socialite\Contracts\User as SocialiteUserContract; 204 | 205 | ->plugin( 206 | FilamentSocialitePlugin::make() 207 | // ... 208 | ->authorizeUserUsing(function (FilamentSocialitePlugin $plugin, SocialiteUserContract $oauthUser) { 209 | // Logic to authorize the user. 210 | return FilamentSocialitePlugin::checkDomainAllowList($plugin, $oauthUser); 211 | }) 212 | // ... 213 | ); 214 | ``` 215 | 216 | ### Change login redirect 217 | 218 | When your panel has [multi-tenancy](https://filamentphp.com/docs/4.x/users/tenancy) enabled, after logging in, the user will be redirected to their [default tenant](https://filamentphp.com/docs/4.x/users/tenancy#setting-the-default-tenant). 219 | If you want to change this behavior, you can call the 'redirectAfterLoginUsing' method on the `FilamentSocialitePlugin`. 220 | 221 | ```php 222 | use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin; 223 | use DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser as FilamentSocialiteUserContract; 224 | use DutchCodingCompany\FilamentSocialite\Models\SocialiteUser; 225 | 226 | FilamentSocialitePlugin::make() 227 | ->redirectAfterLoginUsing(function (string $provider, FilamentSocialiteUserContract $socialiteUser, FilamentSocialitePlugin $plugin) { 228 | // Change the redirect behaviour here. 229 | }); 230 | ``` 231 | 232 | ## Events 233 | 234 | There are a few events dispatched during the authentication process: 235 | 236 | * `InvalidState(InvalidStateException $exception)`: When trying to retrieve the oauth (socialite) user, an invalid state was encountered 237 | * `Login(FilamentSocialiteUserContract $socialiteUser)`: When a user successfully logs in 238 | * `Registered(string $provider, SocialiteUserContract $oauthUser, FilamentSocialiteUserContract $socialiteUser)`: When a user and socialite user is successfully registered and logged in (when enabled in config) 239 | * `RegistrationNotEnabled(string $provider, SocialiteUserContract $oauthUser, ?Auhthenticatable $user)`: When a user tries to login with an unknown account and registration is not enabled 240 | * `SocialiteUserConnected(string $provider, SocialiteUserContract $oauthUser, FilamentSocialiteUserContract $socialiteUser)`: When a socialite user is created for an existing user 241 | * `UserNotAllowed(SocialiteUserContract $oauthUser)`: When a user tries to login with an email which domain is not on the allowlist 242 | 243 | ## Scopes 244 | 245 | Scopes can be added to the provider on the panel, for example: 246 | 247 | ```php 248 | use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin; 249 | use DutchCodingCompany\FilamentSocialite\Provider; 250 | 251 | FilamentSocialitePlugin::make() 252 | ->providers([ 253 | Provider::make('github') 254 | ->label('Github') 255 | ->icon('fab-github') 256 | ->scopes([ 257 | // Add scopes here. 258 | 'read:user', 259 | 'public_repo', 260 | ]), 261 | ]), 262 | ``` 263 | 264 | ## Optional parameters 265 | 266 | You can add [optional parameters](https://laravel.com/docs/10.x/socialite#optional-parameters) to the request by adding a `with` key to the provider on the panel, for example: 267 | 268 | ```php 269 | use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin; 270 | use DutchCodingCompany\FilamentSocialite\Provider; 271 | 272 | FilamentSocialitePlugin::make() 273 | ->providers([ 274 | Provider::make('github') 275 | ->label('Github') 276 | ->icon('fab-github') 277 | ->with([ 278 | // Add scopes here. 279 | // Add optional parameters here. 280 | 'hd' => 'example.com', 281 | ]), 282 | ]), 283 | ``` 284 | ## Visibility 285 | 286 | You can set the visibility of a provider, if it is not visible, buttons will not be rendered. All functionality will still be enabled. 287 | 288 | ```php 289 | use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin; 290 | use DutchCodingCompany\FilamentSocialite\Provider; 291 | 292 | FilamentSocialitePlugin::make() 293 | ->providers([ 294 | Provider::make('github') 295 | ->visible(fn () => true), 296 | ]), 297 | ``` 298 | 299 | ## Stateless Authentication 300 | You can add `stateless` parameters to the provider configuration in the config/services.php config file, for example: 301 | 302 | ```php 303 | 'apple' => [ 304 | 'client_id' => '...', 305 | 'client_secret' => '...', 306 | 'stateless'=>true, 307 | ] 308 | ``` 309 | 310 | **Note:** you cannot use the `state` parameter, as it is used to determine from which Filament panel the user came from. 311 | 312 | ## Changelog 313 | 314 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 315 | 316 | ## Contributing 317 | 318 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 319 | 320 | ## Security Vulnerabilities 321 | 322 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 323 | 324 | ## Credits 325 | - [All Contributors](../../contributors) 326 | 327 | ## License 328 | 329 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 330 | --------------------------------------------------------------------------------