Tabla de contenido

Imagina que cada petición HTTP que llega a tu API pasa por una serie de puertas antes de llegar al controlador. Cada puerta puede revisar la petición, modificarla, rechazarla o simplemente dejarla pasar. En Laravel, esas puertas se llaman middleware.

¿Qué es un middleware?

Un middleware es una capa de código que intercepta las peticiones HTTP antes de que lleguen al controlador (y también puede procesar las respuestas antes de enviarlas al cliente).

Petición → [Middleware 1] → [Middleware 2] → Controlador → Respuesta

Los middleware son perfectos para:

  • Verificar que el usuario está autenticado
  • Comprobar que el usuario tiene los permisos necesarios
  • Limitar la cantidad de peticiones (rate limiting)
  • Registrar logs de actividad
  • Añadir headers de seguridad (CORS, etc.)
  • Validar tokens de API

Middleware incluidos en Laravel

Laravel viene con varios middleware listos para usar:

// Algunos de los middleware predefinidos en Laravel:
'auth'            // Verifica que el usuario está autenticado
'guest'           // Solo permite acceso a usuarios no autenticados
'verified'        // Requiere email verificado
'throttle:60,1'   // Limita a 60 peticiones por minuto
'cors'            // Gestiona las cabeceras CORS

Se aplican en las rutas o grupos de rutas directamente.

Crear un middleware personalizado

Genera el middleware con Artisan:

php artisan make:middleware VerificarTokenAPI

Esto crea el archivo en app/Http/Middleware/VerificarTokenAPI.php:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class VerificarTokenAPI {

    public function handle(Request $request, Closure $next) {
        // Lógica ANTES de llegar al controlador

        $token = $request->header('X-API-Token');

        if (!$token || $token !== config('app.api_token')) {
            return response()->json([
                'error' => 'Token de API inválido o faltante'
            ], 401);
        }

        // Si pasa la verificación, continúa al siguiente middleware o controlador
        $response = $next($request);

        // Lógica DESPUÉS del controlador (opcional)
        $response->headers->set('X-Processed-By', '8devmx-api');

        return $response;
    }
}

El método handle recibe la petición y una función $next que continúa hacia el siguiente middleware o controlador. Si llamas a $next($request), la petición pasa. Si retornas una respuesta directamente (como el response()->json(...) del ejemplo), la petición se detiene ahí.

Registrar y aplicar el middleware

Registrar en app/Http/Kernel.php:

// Como middleware de ruta (se aplica explícitamente)
protected $routeMiddleware = [
    'auth'         => \App\Http\Middleware\Authenticate::class,
    'verificar.token' => \App\Http\Middleware\VerificarTokenAPI::class,
    'check.role'   => \App\Http\Middleware\CheckRole::class,
];

Aplicar en rutas individuales:

// routes/api.php
Route::get('/dashboard', [DashboardController::class, 'index'])
    ->middleware('auth');

Route::post('/articulos', [ArticuloController::class, 'store'])
    ->middleware(['auth', 'verificar.token']);

Aplicar a grupos de rutas:

Route::middleware(['auth', 'verificar.token'])->group(function () {
    Route::get('/usuarios', [UsuarioController::class, 'index']);
    Route::post('/usuarios', [UsuarioController::class, 'store']);
    Route::delete('/usuarios/{id}', [UsuarioController::class, 'destroy']);
});

Aplicar en el constructor del controlador:

class UsuarioController extends Controller {
    
    public function __construct() {
        $this->middleware('auth');
        $this->middleware('check.role:admin')->only(['destroy']);
        $this->middleware('throttle:30,1')->only(['store', 'update']);
    }
}

Middleware de autenticación

Un middleware de autenticación con JWT manual:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Tymon\JWTAuth\Facades\JWTAuth;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;

class AutenticarJWT {

    public function handle(Request $request, Closure $next) {
        try {
            $usuario = JWTAuth::parseToken()->authenticate();
            
            if (!$usuario) {
                return response()->json(['error' => 'Usuario no encontrado'], 404);
            }
            
        } catch (TokenExpiredException $e) {
            return response()->json(['error' => 'Token expirado'], 401);
            
        } catch (TokenInvalidException $e) {
            return response()->json(['error' => 'Token inválido'], 401);
            
        } catch (\Exception $e) {
            return response()->json(['error' => 'Token no proporcionado'], 401);
        }

        return $next($request);
    }
}

Middleware de roles y permisos

Para controlar el acceso según el rol del usuario:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class CheckRole {

    public function handle(Request $request, Closure $next, ...$roles) {
        $usuario = $request->user();

        if (!$usuario) {
            return response()->json(['error' => 'No autenticado'], 401);
        }

        // $roles viene del middleware: 'check.role:admin,editor'
        if (!in_array($usuario->rol, $roles)) {
            return response()->json([
                'error' => 'No tienes permisos para realizar esta acción'
            ], 403);
        }

        return $next($request);
    }
}

Uso en rutas:

// Solo admins
Route::delete('/usuarios/{id}', [UsuarioController::class, 'destroy'])
    ->middleware('check.role:admin');

// Admins o editores
Route::post('/articulos', [ArticuloController::class, 'store'])
    ->middleware('check.role:admin,editor');

Middleware de rate limiting

Laravel incluye throttle por defecto, pero puedes crear uno personalizado:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\RateLimiter;

class LimitarPeticiones {

    public function handle(Request $request, Closure $next, int $maxPeticiones = 60) {
        $clave = 'rate-limit:' . $request->ip();
        
        if (RateLimiter::tooManyAttempts($clave, $maxPeticiones)) {
            $segundos = RateLimiter::availableIn($clave);
            
            return response()->json([
                'error' => 'Demasiadas peticiones',
                'reintentar_en' => "$segundos segundos"
            ], 429);
        }
        
        RateLimiter::hit($clave, 60); // expira en 60 segundos
        
        $response = $next($request);
        
        return $response->withHeaders([
            'X-RateLimit-Limit'     => $maxPeticiones,
            'X-RateLimit-Remaining' => RateLimiter::remaining($clave, $maxPeticiones),
        ]);
    }
}

El middleware es uno de los patrones más elegantes de Laravel. Una vez que lo dominas, organizar la lógica de autorización, autenticación y validación de tu API se vuelve mucho más limpio y mantenible que mezclar todo esa lógica dentro de los controladores.