Advanced Laravel
Advanced Authentication: Multi-Guard & SSO
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.
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.