Table of Contents
- Introduction to Guards
- Understanding Authentication vs Authorization
- Types of Guards
- How Guards Work
- Creating Custom Guards
- Practical Examples
- Best Practices
Introduction to Guards {#introduction}
What are Guards?
Think of Guards as security checkpoints at different entrances to your application. Like a building with multiple entrances, your Laravel app can have different ways for users to authenticate:
Your App Building:
[Web Browser Door] 🚪 -- Session Guard (cookie-based)
|
[Phone App Door] 📱 -- Token Guard (API token)
|
[API Door] 🔌 -- Sanctum Guard (modern tokens)
|
[Admin Door] 👮 -- Custom Guard (special access)
Why Use Guards?
- Different authentication methods for different client types
- Separate user types (customers, admins, vendors)
- Multiple authentication systems in one app
- Enhanced security through isolation
Understanding Authentication vs Authorization {#auth-vs-authz}
Authentication = "Who are you?" 🔐
// Guards handle authentication
Auth::check(); // Are you logged in?
Auth::user(); // Who are you?
Auth::id(); // What's your ID?
Authorization = "What can you do?" 🚦
// Gates and Policies handle authorization
Gate::allows('edit-post', $post); // Can you edit this?
$user->can('delete', $post); // Can user delete this?
$this->authorize('update', $resource); // Throw error if not allowed
Types of Guards {#types-of-guards}
1. Session Guard (Default Web Guard)
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
],
How it works:
- User logs in with credentials
- Laravel creates a session and stores user ID
- Session ID saved in cookie
- Subsequent requests use cookie to identify user
Use cases:
- Traditional web applications
- Server-side rendered pages
- Applications requiring "remember me" functionality
2. Token Guard (API Authentication)
'guards' => [
'api' => [
'driver' => 'token',
'provider' => 'users',
],
],
How it works:
- User receives a token after authentication
- Token sent with each API request
- No session or cookies required
Use cases:
- Mobile applications
- Third-party API access
- Stateless authentication
3. Sanctum Guard (Modern API/SPA)
'guards' => [
'sanctum' => [
'driver' => 'sanctum',
'provider' => 'users',
],
],
Features:
- Cookie-based authentication for SPAs
- Token-based authentication for mobile apps
- Built-in CSRF protection
- API token abilities/scopes
4. Passport Guard (OAuth2 Server)
'guards' => [
'passport' => [
'driver' => 'passport',
'provider' => 'users',
],
],
Features:
- Full OAuth2 server implementation
- Personal access tokens
- Password grant tokens
- Authorization codes
How Guards Work {#how-guards-work}
The Authentication Flow
// 1. User attempts login
$credentials = ['email' => 'user@example.com', 'password' => 'password'];
// 2. Laravel validates credentials
if (Auth::attempt($credentials)) {
// 3. Create session/token
// 4. Store user identification
}
// 5. Subsequent requests
$user = Auth::user(); // Retrieves authenticated user
Behind the Scenes
// How Auth::user() works internally
public function user()
{
// Check if already loaded for this request
if (!is_null($this->user)) {
return $this->user;
}
// Get user ID from session
$id = $this->session->get($this->getName());
// Load user from database
if (!is_null($id)) {
$this->user = $this->provider->retrieveById($id);
}
return $this->user;
}
Guard Configuration
// config/auth.php
return [
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
],
];
Creating Custom Guards {#custom-guards}
Step 1: Create the Guard Class
<?php
namespace App\Auth;
use Illuminate\Http\Request;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\UserProvider;
class MarketingGuard implements Guard
{
use GuardHelpers;
protected $request;
protected $provider;
protected $tokenKey = 'X-Marketing-Token';
public function __construct(UserProvider $provider, Request $request)
{
$this->provider = $provider;
$this->request = $request;
}
public function user()
{
if (!is_null($this->user)) {
return $this->user;
}
$token = $this->getTokenFromRequest();
if (!empty($token)) {
$this->user = $this->provider->retrieveByCredentials([
'api_token' => $token
]);
}
return $this->user;
}
protected function getTokenFromRequest()
{
$token = $this->request->header($this->tokenKey);
if (str_starts_with($token, 'Bearer ')) {
return substr($token, 7);
}
return $token;
}
public function validate(array $credentials = [])
{
if (empty($credentials['api_token'])) {
return false;
}
$user = $this->provider->retrieveByCredentials($credentials);
return !is_null($user);
}
}
Step 2: Create Custom Provider
<?php
namespace App\Auth;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Auth\Authenticatable;
class MarketingUserProvider extends EloquentUserProvider
{
public function retrieveByCredentials(array $credentials)
{
if (empty($credentials['api_token'])) {
return null;
}
return $this->createModel()
->where('api_token', $credentials['api_token'])
->where('is_active', true)
->first();
}
public function validateCredentials(Authenticatable $user, array $credentials)
{
return $user->api_token === $credentials['api_token'];
}
}
Step 3: Register Guard and Provider
// app/Providers/AuthServiceProvider.php
public function boot()
{
// Register custom guard
Auth::extend('marketing', function ($app, $name, array $config) {
$provider = Auth::createUserProvider($config['provider']);
$request = $app->make('request');
return new MarketingGuard($provider, $request);
});
// Register custom provider
Auth::provider('marketing', function ($app, array $config) {
return new MarketingUserProvider(
$app['hash'],
$config['model']
);
});
}
Step 4: Configure in auth.php
// config/auth.php
'guards' => [
'marketing' => [
'driver' => 'marketing',
'provider' => 'marketing_users',
],
],
'providers' => [
'marketing_users' => [
'driver' => 'marketing',
'model' => App\Models\MarketingUser::class,
],
],
IMPORTANT: Name Matching Rules
When creating custom guards, certain names MUST match exactly:
// 1. Guard Driver Names
'guards' => [
'marketing' => [
'driver' => 'marketing', // ← This name...
'provider' => 'marketing_users',
],
],
// Must match this:
Auth::extend('marketing', function ($app, $name, array $config) {
// ↑ This name must match the driver name above
});
// 2. Provider Driver Names
'providers' => [
'marketing_users' => [
'driver' => 'marketing', // ← This name...
'model' => App\Models\MarketingUser::class,
],
],
// Must match this:
Auth::provider('marketing', function ($app, array $config) {
// ↑ This name must match the provider driver name above
});
// 3. Guard Names When Using
Auth::guard('marketing')->user(); // ← Must match guard name in config
// 4. Provider References
'guards' => [
'marketing' => [
'driver' => 'marketing',
'provider' => 'marketing_users', // ← This name...
],
],
'providers' => [
'marketing_users' => [ // ← Must match this provider name
'driver' => 'marketing',
'model' => App\Models\MarketingUser::class,
],
],
Naming Freedom
You have complete freedom with:
- Middleware names in Kernel.php
- Route middleware names
- Class names
- File names
// app/Http/Kernel.php - These can be anything
protected $routeMiddleware = [
'auth.marketing' => \App\Http\Middleware\AuthenticateMarketing::class,
'marketing' => \App\Http\Middleware\AuthenticateMarketing::class,
'mkt.auth' => \App\Http\Middleware\AuthenticateMarketing::class,
'check.marketing' => \App\Http\Middleware\AuthenticateMarketing::class,
];
Complete Example with Name Matching
// config/auth.php
'guards' => [
'custom_auth' => [ // Guard name (your choice)
'driver' => 'my_driver', // Must match Auth::extend()
'provider' => 'my_users', // Must match a provider below
],
],
'providers' => [
'my_users' => [ // Provider name (must match above)
'driver' => 'my_provider', // Must match Auth::provider()
'model' => User::class,
],
],
// AuthServiceProvider.php
Auth::extend('my_driver', function () { // Must match driver in guard
// Return guard instance
});
Auth::provider('my_provider', function () { // Must match driver in provider
// Return provider instance
});
// Using the guard
Auth::guard('custom_auth')->user(); // Must use exact guard name
// Middleware (can be named anything)
'my.auth' => MyAuthMiddleware::class, // Your choice of name
Step 5: Create Middleware
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class AuthenticateMarketing
{
public function handle($request, Closure $next)
{
if (!Auth::guard('marketing')->check()) {
return response()->json(['error' => 'Unauthorized'], 401);
}
Auth::shouldUse('marketing');
return $next($request);
}
}
Practical Examples {#practical-examples}
Multi-Guard Application
// E-commerce application with multiple user types
// config/auth.php
'guards' => [
'customer' => [
'driver' => 'session',
'provider' => 'customers',
],
'vendor' => [
'driver' => 'sanctum',
'provider' => 'vendors',
],
'admin' => [
'driver' => 'session',
'provider' => 'admins',
],
'delivery' => [
'driver' => 'token',
'provider' => 'drivers',
],
],
// Routes for different user types
// routes/customer.php
Route::middleware(['auth:customer'])->group(function () {
Route::get('/orders', [CustomerOrderController::class, 'index']);
Route::get('/profile', [CustomerProfileController::class, 'show']);
});
// routes/vendor.php
Route::middleware(['auth:vendor'])->group(function () {
Route::get('/products', [VendorProductController::class, 'index']);
Route::post('/inventory', [VendorInventoryController::class, 'update']);
});
// routes/admin.php
Route::middleware(['auth:admin'])->group(function () {
Route::get('/dashboard', [AdminDashboardController::class, 'index']);
Route::get('/users', [AdminUserController::class, 'index']);
});
// routes/api.php
Route::middleware(['auth:delivery'])->prefix('driver')->group(function () {
Route::get('/orders', [DriverOrderController::class, 'index']);
Route::post('/status', [DriverStatusController::class, 'update']);
});
Smart Guard Detection
// Middleware that detects which guard to use
class SmartAuthMiddleware
{
public function handle($request, $next)
{
// Check for API token
if ($request->bearerToken()) {
Auth::shouldUse('api');
}
// Check for vendor header
elseif ($request->header('X-Vendor-Token')) {
Auth::shouldUse('vendor');
}
// Check for session cookie
elseif ($request->hasCookie('laravel_session')) {
Auth::shouldUse('web');
}
return $next($request);
}
}
Using Multiple Guards in Controllers
class ProfileController extends Controller
{
public function show(Request $request)
{
// Get current guard
$guardName = Auth::getDefaultDriver();
// Different responses for different guards
switch ($guardName) {
case 'web':
return view('profile.web', ['user' => Auth::user()]);
case 'api':
return response()->json(['user' => Auth::user()]);
case 'vendor':
return response()->json([
'vendor' => Auth::user(),
'products' => Auth::user()->products
]);
default:
return response('Unknown client', 400);
}
}
}
Best Practices {#best-practices}
1. Use Appropriate Guards for Use Cases
// Web browsers - use session guard
'customer' => ['driver' => 'session'],
// Mobile apps - use sanctum/token
'mobile' => ['driver' => 'sanctum'],
// Third-party APIs - use passport/custom
'external' => ['driver' => 'passport'],
2. Consistent Naming Conventions
// Good - follows pattern
'guards' => [
'customer' => [...],
'customer_api' => [...],
],
'providers' => [
'customers' => [...],
'customer_api_users' => [...],
],
// Bad - inconsistent
'guards' => [
'user' => [...],
'apiCustomer' => [...],
],
3. Separate Routes by Guard Type
// routes/web.php - Session-based
Route::middleware(['auth:web'])->group(function () {
// Web routes
});
// routes/api.php - Token-based
Route::middleware(['auth:api'])->group(function () {
// API routes
});
// routes/admin.php - Admin area
Route::middleware(['auth:admin'])->group(function () {
// Admin routes
});
4. Use Guard-Specific Middleware
// Create specific middleware for each guard
class EnsureCustomerAuthenticated
{
public function handle($request, $next)
{
if (!Auth::guard('customer')->check()) {
return redirect('/customer/login');
}
return $next($request);
}
}
5. Handle Guard-Specific Login Pages
// app/Exceptions/Handler.php
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
// Redirect to appropriate login page based on guard
$guard = $exception->guards()[0] ?? null;
switch ($guard) {
case 'admin':
return redirect('/admin/login');
case 'vendor':
return redirect('/vendor/login');
default:
return redirect('/login');
}
}
6. Security Considerations
// Always validate tokens properly
public function validateToken($token)
{
// Check token format
if (!preg_match('/^[a-zA-Z0-9]{64}$/', $token)) {
return false;
}
// Check token expiry
$user = User::where('api_token', $token)
->where('token_expires_at', '>', now())
->first();
return $user !== null;
}
// Use proper session configuration
// config/session.php
'lifetime' => 120,
'expire_on_close' => false,
'encrypt' => true,
'secure' => true, // HTTPS only
'same_site' => 'lax',
Common Patterns and Solutions
Pattern 1: Multi-tenant Application
// Different guards for different tenant types
'guards' => [
'tenant_a' => [
'driver' => 'session',
'provider' => 'tenant_a_users',
],
'tenant_b' => [
'driver' => 'session',
'provider' => 'tenant_b_users',
],
],
// Route middleware
Route::domain('{tenant}.app.com')->group(function () {
Route::middleware(['auth:tenant_' . request()->tenant])->group(function () {
// Tenant-specific routes
});
});
Pattern 2: API Versioning with Guards
// Different guards for API versions
'guards' => [
'api_v1' => [
'driver' => 'token',
'provider' => 'users',
],
'api_v2' => [
'driver' => 'sanctum',
'provider' => 'users',
],
],
// Versioned routes
Route::prefix('v1')->middleware(['auth:api_v1'])->group(function () {
// V1 API routes
});
Route::prefix('v2')->middleware(['auth:api_v2'])->group(function () {
// V2 API routes
});
Pattern 3: Social Authentication
// Custom social guard
class SocialGuard implements Guard
{
public function user()
{
$token = $this->request->bearerToken();
// Verify with social provider
$socialUser = $this->verifySocialToken($token);
// Find or create local user
return User::firstOrCreate([
'email' => $socialUser->email,
], [
'name' => $socialUser->name,
'provider' => $socialUser->provider,
]);
}
}
Debugging Guards
Common Issues and Solutions
// Debug current guard
dd(Auth::getDefaultDriver());
// Check if user is authenticated
if (Auth::guard('custom')->check()) {
dd(Auth::guard('custom')->user());
}
// Debug authentication failures
try {
$user = Auth::guard('api')->user();
} catch (\Exception $e) {
Log::error('Auth failed: ' . $e->getMessage());
}
// Test specific guard
Route::get('/test-guard/{guard}', function ($guard) {
return [
'guard' => $guard,
'authenticated' => Auth::guard($guard)->check(),
'user' => Auth::guard($guard)->user(),
];
});
Summary
Laravel Guards provide a flexible authentication system that allows you to:
- Support multiple authentication methods simultaneously
- Separate different user types with different authentication needs
- Implement custom authentication logic for special requirements
- Maintain security through isolation of authentication systems
Key takeaways:
- Guards define HOW users authenticate (session, token, etc.)
- Providers define WHERE user data comes from
- Middleware connects routes to specific guards
- Multiple guards can coexist in the same application
- Custom guards extend Laravel's authentication capabilities
By understanding and properly implementing guards, you can build secure, flexible authentication systems that meet diverse application requirements.
Top comments (0)