directorytree/bartender
Opinionated Socialite authentication for Laravel. Ships ready-made routes (/auth/{driver}/redirect, /callback), a controller, migrations to store provider IDs and optional access/refresh tokens, and customizable hooks for features like soft deletes and email verification.
Installation:
composer require directorytree/bartender
php artisan vendor:publish --provider="DirectoryTree\Bartender\BartenderServiceProvider"
php artisan migrate
Configure Routes:
// routes/web.php
use DirectoryTree\Bartender\Facades\Bartender;
Bartender::routes(); // Adds `/auth/{driver}/redirect` and `/auth/{driver}/callback`
Register Providers:
// config/services.php
'google' => [
'redirect' => '/auth/google/callback',
// ... other config
],
Serve Providers:
// app/Providers/AppServiceProvider.php
Bartender::serve('google'); // Register with default handler
First Use Case: Add login links in Blade:
<a href="{{ route('auth.driver.redirect', 'google') }}">Login with Google</a>
Redirect Flow:
/auth/{driver}/redirect → Socialite provider → /auth/{driver}/callback.Custom User Model:
User model isn’t in App\Models, set it in AuthServiceProvider:
Bartender::setUserModel(\App\User::class);
Token Storage (Optional):
StoresProviderTokens in your User model and encrypt tokens:
protected $hidden = ['provider_access_token', 'provider_refresh_token'];
protected function casts(): array { return ['provider_access_token' => 'encrypted']; }
Provider-Specific Scopes:
Extend UserProviderHandler to modify scopes (e.g., Microsoft):
class MicrosoftUserHandler extends UserProviderHandler {
public function redirect(Provider $provider, string $driver): RedirectResponse {
$provider->scopes(['Mail.ReadWrite']);
return parent::redirect($provider, $driver);
}
}
Register it:
Bartender::serve('microsoft', MicrosoftUserHandler::class);
User Creation Logic:
Bind a custom ProviderRepository to override exists() or updateOrCreate():
$this->app->bind(ProviderRepository::class, CustomUserProviderRepository::class);
Post-Auth Redirects:
Customize redirects in ProviderRedirector:
public function userAuthenticated(Authenticatable $user, SocialiteUser $socialite, string $driver): RedirectResponse {
Auth::login($user);
Session::regenerate(); // Critical for security
return redirect()->route('dashboard');
}
Laravel Socialite Providers:
Ensure each provider (e.g., GitHub, Facebook) is properly installed before using Bartender::serve().
Driver [X] not supported errors if the provider isn’t registered.Testing:
Use Bartender::serve() with mock providers in tests:
Bartender::serve('mock', MockProviderHandler::class);
Multi-Tenant Apps:
Extend ProviderRepository to scope users by tenant:
public function exists(string $driver, SocialiteUser $user): bool {
return User::withTrashed()->where('email', $user->getEmail())
->where('tenant_id', request()->tenantId)->exists();
}
Missing Bartender::serve():
Routing requirement for "driver" cannot be empty.AppServiceProvider::boot().Token Storage Mismatch:
StoresProviderTokens, Bartender will throw an error.Session Fixation:
Session::regenerate() in ProviderRedirector leaves users vulnerable.userAuthenticated().Soft Deletes:
exists() in ProviderRepository to change this:
public function exists(string $driver, SocialiteUser $user): bool {
return User::where('email', $user->getEmail())->doesntExist(); // Never restore
}
Email Verification:
updateOrCreate():
$user->email_verified_at = null;
Provider Callback Failures:
storage/logs/laravel.log for Socialite errors (e.g., invalid credentials).services.php config matches the provider’s requirements.Custom Handler Not Triggered:
AppServiceProvider).Token Encryption Issues:
encrypted cast is applied to the model.provider_access_token column is of type text (not string).Dynamic Provider Registration: Register providers dynamically based on config:
foreach (config('auth.providers') as $driver) {
Bartender::serve($driver);
}
Provider-Specific Logic: Use dependency injection in custom handlers:
public function __construct(
protected Request $request,
protected TenantService $tenantService
) {}
Fallback to Email/Password:
Extend ProviderRedirector to fall back if Socialite fails:
public function unableToAuthenticateUser(Exception $e, string $driver): RedirectResponse {
return redirect()->route('login.email');
}
Webhook Validation:
For providers like GitHub, validate webhooks by extending ProviderHandler:
public function callback(Provider $provider, string $driver): RedirectResponse {
if (!$this->validateWebhook($provider->user())) {
abort(403);
}
return parent::callback($provider, $driver);
}
Route Naming:
Bartender uses auth.driver.redirect and auth.driver.callback. Override in routes/web.php:
Bartender::routes(['prefix' => 'social']);
Provider Driver Names:
Ensure services.php keys match the driver names used in Bartender::serve() (case-sensitive).
Password Hashing:
Bartender uses Laravel’s Hash::make() by default. To customize:
// In ProviderRepository
public function updateOrCreate(string $driver, SocialiteUser $user): Authenticatable {
$user->password = Hash::make('default_password');
// ...
}
```markdown
### Pro Tips
- **Leverage `SocialiteUser`**:
Access raw provider data in `ProviderRepository`:
```php
$user->name = $socialiteUser->getName();
$user->avatar = $socialiteUser->getAvatar();
Rate Limiting:
Protect /auth/{driver}/callback from brute force:
Route::middleware(['throttle:10,1'])->group(function () {
Bartender::routes();
});
Testing Callbacks: Mock Socialite responses in tests:
$this->mock(Socialite::class)->shouldReceive('driver')->andReturnSelf()
->shouldReceive('user')->andReturn($mockSocialiteUser);
Multi-Provider Logins:
Track the last used provider in the User model:
protected $casts = ['last_provider' => 'string'];
// In ProviderRepository:
$user->last_provider = $driver;
How can I help you explore Laravel packages today?