Advanced Laravel

Advanced Authentication: Multi-Guard & SSO

20 min Lesson 7 of 40

Advanced Authentication: Multi-Guard & SSO

Implement advanced authentication systems including multiple guards, custom user providers, Single Sign-On (SSO), OAuth2, and API authentication with Laravel Passport and Sanctum.

Understanding Authentication Guards

Guards define how users are authenticated for each request. Laravel supports multiple authentication guards simultaneously:

// config/auth.php return [ 'defaults' => [ 'guard' => 'web', 'passwords' => 'users', ], 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'token', 'provider' => 'users', 'hash' => false, ], 'admin' => [ 'driver' => 'session', 'provider' => 'admins', ], 'customer' => [ 'driver' => 'session', 'provider' => 'customers', ], ], 'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\Models\User::class, ], 'admins' => [ 'driver' => 'eloquent', 'model' => App\Models\Admin::class, ], 'customers' => [ 'driver' => 'eloquent', 'model' => App\Models\Customer::class, ], ], ];

Using Multiple Guards

Access different guards in controllers and middleware:

namespace App\Http\Controllers; class AuthController extends Controller { // Login with specific guard public function adminLogin(Request $request) { $credentials = $request->only('email', 'password'); if (Auth::guard('admin')->attempt($credentials)) { return redirect()->route('admin.dashboard'); } return back()->withErrors(['email' => 'Invalid credentials']); } // Check authentication for specific guard public function dashboard() { if (Auth::guard('admin')->check()) { $admin = Auth::guard('admin')->user(); return view('admin.dashboard', compact('admin')); } return redirect()->route('admin.login'); } // Logout from specific guard public function logout() { Auth::guard('admin')->logout(); return redirect()->route('admin.login'); } // Multiple guards authentication check public function profile() { if (Auth::guard('admin')->check()) { $user = Auth::guard('admin')->user(); $userType = 'admin'; } elseif (Auth::guard('customer')->check()) { $user = Auth::guard('customer')->user(); $userType = 'customer'; } else { return redirect()->route('login'); } return view('profile', compact('user', 'userType')); } }
Pro Tip: Use route middleware to protect routes with specific guards: Route::middleware('auth:admin')->group(...)

Custom User Providers

Create custom authentication providers for non-standard user sources:

// app/Providers/CustomUserProvider.php namespace App\Providers; use Illuminate\Contracts\Auth\UserProvider; use Illuminate\Contracts\Auth\Authenticatable; class LdapUserProvider implements UserProvider { protected $ldapConnection; public function __construct($ldapConfig) { $this->ldapConnection = ldap_connect($ldapConfig['host']); ldap_set_option($this->ldapConnection, LDAP_OPT_PROTOCOL_VERSION, 3); } public function retrieveById($identifier) { $filter = "(uid={$identifier})"; $result = ldap_search($this->ldapConnection, 'dc=example,dc=com', $filter); $entries = ldap_get_entries($this->ldapConnection, $result); if ($entries['count'] > 0) { return $this->createUserFromLdap($entries[0]); } return null; } public function retrieveByCredentials(array $credentials) { $username = $credentials['username']; $filter = "(uid={$username})"; $result = ldap_search($this->ldapConnection, 'dc=example,dc=com', $filter); $entries = ldap_get_entries($this->ldapConnection, $result); if ($entries['count'] > 0) { return $this->createUserFromLdap($entries[0]); } return null; } public function validateCredentials(Authenticatable $user, array $credentials) { $username = $credentials['username']; $password = $credentials['password']; $dn = "uid={$username},ou=users,dc=example,dc=com"; return @ldap_bind($this->ldapConnection, $dn, $password); } protected function createUserFromLdap($ldapUser) { return new \App\Models\LdapUser([ 'id' => $ldapUser['uid'][0], 'name' => $ldapUser['cn'][0], 'email' => $ldapUser['mail'][0], ]); } public function retrieveByToken($identifier, $token) { return null; } public function updateRememberToken(Authenticatable $user, $token) {} public function rehashPasswordIfRequired(Authenticatable $user, array $credentials, bool $force = false) {} }

Registering Custom Provider

Register the custom provider in AuthServiceProvider:

// app/Providers/AuthServiceProvider.php namespace App\Providers; use Illuminate\Support\Facades\Auth; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; class AuthServiceProvider extends ServiceProvider { public function boot() { $this->registerPolicies(); // Register custom LDAP provider Auth::provider('ldap', function ($app, array $config) { return new LdapUserProvider($config); }); } } // config/auth.php 'providers' => [ 'ldap_users' => [ 'driver' => 'ldap', 'host' => env('LDAP_HOST', 'ldap.example.com'), 'port' => env('LDAP_PORT', 389), ], ], 'guards' => [ 'ldap' => [ 'driver' => 'session', 'provider' => 'ldap_users', ], ],
Note: Custom providers must implement the Illuminate\Contracts\Auth\UserProvider interface. The authenticated user must implement Illuminate\Contracts\Auth\Authenticatable.

Single Sign-On (SSO) with SAML

Implement SAML-based SSO for enterprise authentication:

// composer require onelogin/php-saml // app/Http/Controllers/SamlController.php namespace App\Http\Controllers; use OneLogin\Saml2\Auth as Saml2Auth; class SamlController extends Controller { protected function getSamlAuth() { $settings = [ 'sp' => [ 'entityId' => config('saml.sp.entity_id'), 'assertionConsumerService' => [ 'url' => route('saml.acs'), ], 'singleLogoutService' => [ 'url' => route('saml.sls'), ], ], 'idp' => [ 'entityId' => config('saml.idp.entity_id'), 'singleSignOnService' => [ 'url' => config('saml.idp.sso_url'), ], 'x509cert' => config('saml.idp.x509cert'), ], ]; return new Saml2Auth($settings); } public function login() { $auth = $this->getSamlAuth(); $auth->login(route('dashboard')); } public function acs() { $auth = $this->getSamlAuth(); $auth->processResponse(); $errors = $auth->getErrors(); if (!empty($errors)) { return redirect()->route('login') ->withErrors(['saml' => implode(', ', $errors)]); } if (!$auth->isAuthenticated()) { return redirect()->route('login') ->withErrors(['saml' => 'Not authenticated']); } $attributes = $auth->getAttributes(); $user = $this->findOrCreateUser($attributes); Auth::login($user); return redirect()->route('dashboard'); } protected function findOrCreateUser($attributes) { $email = $attributes['email'][0] ?? null; $user = User::where('email', $email)->first(); if (!$user) { $user = User::create([ 'name' => $attributes['name'][0] ?? '', 'email' => $email, 'saml_id' => $attributes['uid'][0] ?? null, ]); } return $user; } public function logout() { $auth = $this->getSamlAuth(); Auth::logout(); $auth->logout(route('login')); } }

OAuth2 with Laravel Socialite

Integrate social login providers using Socialite:

// composer require laravel/socialite // config/services.php return [ 'google' => [ 'client_id' => env('GOOGLE_CLIENT_ID'), 'client_secret' => env('GOOGLE_CLIENT_SECRET'), 'redirect' => env('GOOGLE_REDIRECT_URL'), ], 'github' => [ 'client_id' => env('GITHUB_CLIENT_ID'), 'client_secret' => env('GITHUB_CLIENT_SECRET'), 'redirect' => env('GITHUB_REDIRECT_URL'), ], ]; // app/Http/Controllers/SocialAuthController.php namespace App\Http\Controllers; use Laravel\Socialite\Facades\Socialite; class SocialAuthController extends Controller { public function redirectToProvider($provider) { return Socialite::driver($provider) ->scopes(['read:user', 'user:email']) ->redirect(); } public function handleProviderCallback($provider) { try { $socialUser = Socialite::driver($provider)->user(); } catch (\Exception $e) { return redirect()->route('login') ->withErrors(['oauth' => 'Failed to authenticate']); } $user = $this->findOrCreateUser($socialUser, $provider); Auth::login($user, true); return redirect()->route('dashboard'); } protected function findOrCreateUser($socialUser, $provider) { // Check if user exists with this social account $account = SocialAccount::where('provider', $provider) ->where('provider_id', $socialUser->getId()) ->first(); if ($account) { return $account->user; } // Check if user exists with this email $user = User::where('email', $socialUser->getEmail())->first(); if (!$user) { $user = User::create([ 'name' => $socialUser->getName(), 'email' => $socialUser->getEmail(), 'avatar' => $socialUser->getAvatar(), 'email_verified_at' => now(), ]); } // Create social account link SocialAccount::create([ 'user_id' => $user->id, 'provider' => $provider, 'provider_id' => $socialUser->getId(), 'token' => $socialUser->token, 'refresh_token' => $socialUser->refreshToken, ]); return $user; } }
Warning: Always validate OAuth tokens and handle edge cases like email conflicts, revoked tokens, and provider API changes.

Laravel Passport for API Authentication

Implement OAuth2 server with Laravel Passport:

// composer require laravel/passport // Run migrations and install php artisan migrate php artisan passport:install // app/Models/User.php use Laravel\Passport\HasApiTokens; class User extends Authenticatable { use HasApiTokens, Notifiable; } // app/Providers/AuthServiceProvider.php use Laravel\Passport\Passport; public function boot() { $this->registerPolicies(); // Token lifetimes Passport::tokensExpireIn(now()->addDays(15)); Passport::refreshTokensExpireIn(now()->addDays(30)); Passport::personalAccessTokensExpireIn(now()->addMonths(6)); // Define scopes Passport::tokensCan([ 'read-posts' => 'Read posts', 'create-posts' => 'Create posts', 'delete-posts' => 'Delete posts', ]); Passport::setDefaultScope(['read-posts']); } // config/auth.php 'guards' => [ 'api' => [ 'driver' => 'passport', 'provider' => 'users', ], ], // Protecting routes with scopes Route::middleware(['auth:api', 'scopes:create-posts'])->group(function () { Route::post('/posts', [PostController::class, 'store']); }); // Checking scopes in controller public function store(Request $request) { if ($request->user()->tokenCan('create-posts')) { // Create post } return response()->json(['error' => 'Insufficient permissions'], 403); }

Password Grant Tokens

Issue tokens via username and password:

// app/Http/Controllers/Api/AuthController.php namespace App\Http\Controllers\Api; use Illuminate\Support\Facades\Http; class AuthController extends Controller { public function login(Request $request) { $request->validate([ 'email' => 'required|email', 'password' => 'required', ]); $response = Http::asForm()->post(config('app.url') . '/oauth/token', [ 'grant_type' => 'password', 'client_id' => config('passport.password_client_id'), 'client_secret' => config('passport.password_client_secret'), 'username' => $request->email, 'password' => $request->password, 'scope' => '*', ]); if ($response->failed()) { return response()->json([ 'message' => 'Invalid credentials' ], 401); } return $response->json(); } public function refresh(Request $request) { $request->validate(['refresh_token' => 'required']); $response = Http::asForm()->post(config('app.url') . '/oauth/token', [ 'grant_type' => 'refresh_token', 'refresh_token' => $request->refresh_token, 'client_id' => config('passport.password_client_id'), 'client_secret' => config('passport.password_client_secret'), 'scope' => '*', ]); return $response->json(); } public function logout(Request $request) { $request->user()->token()->revoke(); return response()->json(['message' => 'Logged out successfully']); } }
Exercise 1: Implement a multi-tenant application with separate authentication for:
1. Super admin (manages all tenants)
2. Tenant admin (manages their organization)
3. Regular users (access their tenant's resources)
Create three guards, three user models, and middleware to enforce tenant isolation. Add login pages for each user type.
Exercise 2: Build a custom user provider that authenticates against a MongoDB database. Implement all required methods and register it in AuthServiceProvider. Create a guard using this provider and test login/logout functionality.
Exercise 3: Integrate Google and GitHub OAuth login using Socialite. Allow users to link multiple social accounts to one email. Handle edge cases like existing email conflicts, missing email from provider, and account unlinking. Add a profile page showing all linked accounts.