Middleware
Middleware
Introducción
Middleware proporciona un mecanismo conveniente para inspeccionar y filtrar las solicitudes HTTP que entran en tu aplicación. Por ejemplo, Laravel incluye un middleware que verifica si el usuario de tu aplicación está autenticado. Si el usuario no está autenticado, el middleware redirigirá al usuario a la pantalla de inicio de sesión de tu aplicación. Sin embargo, si el usuario está autenticado, el middleware permitirá que la solicitud continúe hacia la aplicación.
Además de la autenticación, se pueden escribir middleware adicionales para realizar diversas tareas. Por ejemplo, un middleware de registro podría registrar todas las solicitudes entrantes a tu aplicación. Laravel incluye varios middleware, como los de autenticación y protección CSRF; sin embargo, los middleware definidos por el usuario generalmente se encuentran en el directorio app/Http/Middleware de tu aplicación.
Defining Middleware
Para definir un nuevo middleware, puedes usar el comando Artisan make:middleware, por ejemplo:
php artisan make:middleware EnsureTokenIsValid
Este comando colocará una nueva clase EnsureTokenIsValid dentro de tu directorio app/Http/Middleware. En este middleware, solo se permitirá el acceso a la ruta si el token proporcionado coincide con un valor específico. De lo contrario, se redirigirá a los usuarios de vuelta a la URI de inicio:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureTokenIsValid
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if ($request->input('token') !== 'my-secret-token') {
return redirect('home');
}
return $next($request);
}
}
Como puedes ver, si el token dado no coincide con nuestro token secreto, el middleware devolverá una redirección HTTP al cliente; de lo contrario, la solicitud se pasará más adelante en la aplicación. Para permitir que la solicitud continúe en la aplicación (permitir que el middleware “pase”), debes llamar al callback $next con el $request.
Es mejor visualizar el middleware como una serie de “capas” que las solicitudes HTTP deben atravesar antes de llegar a tu aplicación. Cada capa puede examinar la solicitud e incluso rechazarla completamente.
⚠️ Todos los middleware se resuelven a través del contenedor de servicios, por lo que puedes hacer type-hinting de cualquier dependencia que necesites en el constructor de un middleware.
Middleware and Responses
Los middleware pueden realizar tareas antes o después de pasar la solicitud más profundamente en la aplicación. Por ejemplo, un middleware podría realizar alguna tarea antes de que la solicitud sea manejada por la aplicación:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class BeforeMiddleware
{
public function handle(Request $request, Closure $next): Response
{
// Perform action
return $next($request);
}
}
Sin embargo, este middleware podría realizar su tarea después de que la solicitud sea manejada por la aplicación:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class AfterMiddleware
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
// Perform action
return $response;
}
}
Registering Middleware
Global Middleware
Si deseas que un middleware se ejecute en cada solicitud HTTP a tu aplicación, puedes agregarlo al stack global de middleware en el archivo bootstrap/app.php de tu aplicación:
use App\Http\Middleware\EnsureTokenIsValid;
->withMiddleware(function (Middleware $middleware) {
$middleware->append(EnsureTokenIsValid::class);
})
El objeto $middleware proporcionado al cierre withMiddleware es una instancia de Illuminate\Foundation\Configuration\Middleware y es responsable de gestionar el middleware asignado a las rutas de tu aplicación. El método append agrega el middleware al final de la lista de middleware globales. Si deseas agregar un middleware al principio de la lista, debes usar el método prepend.
Manually Managing Laravel’s Default Global Middleware
Si deseas gestionar manualmente el stack global de middleware de Laravel, puedes proporcionar el stack global predeterminado de middleware de Laravel al método use y luego ajustarlo según sea necesario.
->withMiddleware(function (Middleware $middleware) {
$middleware->use([
// \Illuminate\Http\Middleware\TrustHosts::class,
\Illuminate\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Http\Middleware\ValidatePostSize::class,
\Illuminate\Foundation\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
]);
})
Assigning Middleware to Routes
Si deseas asignar middleware a rutas específicas, puedes invocar el método middleware al definir la ruta:
use App\Http\Middleware\EnsureTokenIsValid;
Route::get('/profile', function () {
// ...
})->middleware(EnsureTokenIsValid::class);
Puedes asignar múltiples middleware a la ruta pasando un arreglo de nombres de middleware al método middleware:
Route::get('/', function () {
// ...
})->middleware([First::class, Second::class]);
Excluding Middleware
Cuando asignas middleware a un grupo de rutas, a veces necesitas evitar que el middleware se aplique a una ruta individual dentro del grupo. Puedes lograr esto usando el método withoutMiddleware:
use App\Http\Middleware\EnsureTokenIsValid;
Route::middleware([EnsureTokenIsValid::class])->group(function () {
Route::get('/', function () {
// ...
});
Route::get('/profile', function () {
// ...
})->withoutMiddleware([EnsureTokenIsValid::class]);
});
También puedes excluir un conjunto dado de middleware de todo un grupo de definiciones de ruta:
use App\Http\Middleware\EnsureTokenIsValid;
Route::withoutMiddleware([EnsureTokenIsValid::class])->group(function () {
Route::get('/profile', function () {
// ...
});
});
El método withoutMiddleware solo puede eliminar middleware de rutas y no se aplica al middleware global.
Middleware Groups
A veces puedes querer agrupar varios middleware bajo una sola clave para facilitar su asignación a rutas. Puedes lograr esto usando el método appendToGroup dentro del archivo bootstrap/app.php de tu aplicación:
use App\Http\Middleware\First;
use App\Http\Middleware\Second;
->withMiddleware(function (Middleware $middleware) {
$middleware->appendToGroup('group-name', [
First::class,
Second::class,
]);
$middleware->prependToGroup('group-name', [
First::class,
Second::class,
]);
})
Los grupos de middleware pueden asignarse a rutas y acciones de controlador utilizando la misma sintaxis que los middleware individuales:
Route::get('/', function () {
// ...
})->middleware('group-name');
Route::middleware(['group-name'])->group(function () {
// ...
});
Laravel’s Default Middleware Groups
Laravel incluye grupos de middleware web y api predefinidos que contienen middleware comunes que puedes querer aplicar a tus rutas web y API. Recuerda, Laravel aplica automáticamente estos grupos de middleware a los archivos correspondientes routes/web.php y routes/api.php de tu aplicación:
- Grupo de Middleware
web- Illuminate\Cookie\Middleware\EncryptCookies
- Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse
- Illuminate\Session\Middleware\StartSession
- Illuminate\View\Middleware\ShareErrorsFromSession
- Illuminate\Foundation\Http\Middleware\ValidateCsrfToken
- Illuminate\Routing\Middleware\SubstituteBindings
- Grupo de Middleware
api- Illuminate\Routing\Middleware\SubstituteBindings
Si deseas agregar al principio o al final middleware en estos grupos, puedes usar los métodos web y api en tu archivo bootstrap/app.php. Estos métodos son alternativas convenientes al método appendToGroup:
use App\Http\Middleware\EnsureTokenIsValid;
use App\Http\Middleware\EnsureUserIsSubscribed;
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
EnsureUserIsSubscribed::class,
]);
$middleware->api(prepend: [
EnsureTokenIsValid::class,
]);
})
También puedes reemplazar una de las entradas de middleware del grupo predeterminado de Laravel con un middleware personalizado:
use App\Http\Middleware\StartCustomSession;
use Illuminate\Session\Middleware\StartSession;
$middleware->web(replace: [
StartSession::class => StartCustomSession::class,
]);
O eliminar un middleware del grupo:
$middleware->web(remove: [
StartSession::class,
]);
Manually Managing Laravel’s Default Middleware Groups
Si deseas gestionar manualmente todos los middleware dentro de los grupos web y api predeterminados de Laravel, puedes redefinir completamente los grupos. El siguiente ejemplo define los grupos de middleware web y api con su middleware predeterminado, permitiéndote personalizarlos según sea necesario:
->withMiddleware(function (Middleware $middleware) {
$middleware->group('web', [
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
]);
$middleware->group('api', [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
// 'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
]);
})
⚠️ Por defecto, los grupos de middleware web y api se aplican automáticamente a los archivos correspondientes routes/web.php y routes/api.php de tu aplicación mediante el archivo bootstrap/app.php.
Middleware Aliases
Puedes asignar alias a middleware en el archivo bootstrap/app.php de tu aplicación. Los alias de middleware te permiten definir un alias corto para una clase de middleware dada, lo cual es especialmente útil para middleware con nombres de clase largos:
use App\Http\Middleware\EnsureUserIsSubscribed;
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'subscribed' => EnsureUserIsSubscribed::class
]);
})
Una vez que se haya definido el alias de middleware en el archivo bootstrap/app.php de tu aplicación, puedes usar el alias al asignar el middleware a rutas:
Route::get('/profile', function () {
// ...
})->middleware('subscribed');
Para mayor comodidad, algunos de los middleware integrados de Laravel tienen alias por defecto. Por ejemplo, auth es un alias para el middleware Illuminate\Auth\Middleware\Authenticate. A continuación se muestra una lista de los alias de middleware predeterminados:
| Alias | Middleware |
|---|---|
auth |
Illuminate\Auth\Middleware\Authenticate |
auth.basic |
Illuminate\Auth\Middleware\AuthenticateWithBasicAuth |
auth.session |
Illuminate\Session\Middleware\AuthenticateSession |
cache.headers |
Illuminate\Http\Middleware\SetCacheHeaders |
can |
Illuminate\Auth\Middleware\Authorize |
guest |
Illuminate\Auth\Middleware\RedirectIfAuthenticated |
password.confirm |
Illuminate\Auth\Middleware\RequirePassword |
precognitive |
Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests |
signed |
Illuminate\Routing\Middleware\ValidateSignature |
subscribed |
\Spark\Http\Middleware\VerifyBillableIsSubscribed |
throttle |
Illuminate\Routing\Middleware\ThrottleRequests or Illuminate\Routing\Middleware\ThrottleRequestsWithRedis |
verified |
Illuminate\Auth\Middleware\EnsureEmailIsVerified |
Sorting Middleware
Raramente puedes necesitar que tu middleware se ejecute en un orden específico pero no tener control sobre su orden cuando se asignan a la ruta. En estas situaciones, puedes especificar la prioridad de tu middleware usando el método priority en el archivo bootstrap/app.php de tu aplicación:
->withMiddleware(function (Middleware $middleware) {
$middleware->priority([
\Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
\Illuminate\Auth\Middleware\Authorize::class,
]);
})
Middleware Parameters
El middleware también puede recibir parámetros adicionales. Por ejemplo, si tu aplicación necesita verificar que el usuario autenticado tenga un determinado “rol” antes de realizar una acción, podrías crear un middleware EnsureUserHasRole que reciba el nombre del rol como un argumento adicional.
Los parámetros adicionales de middleware se pasarán al middleware después del argumento $next:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureUserHasRole
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next, string $role): Response
{
if (! $request->user()->hasRole($role)) {
// Redirect...
}
return $next($request);
}
}
Los parámetros de middleware se pueden especificar al definir la ruta separando el nombre del middleware y los parámetros
Route::put('/post/{id}', function (string $id) {
// ...
})->middleware('role:editor');
Multiples parametros pueden ser delimitados por comas
Route::put('/post/{id}', function (string $id) {
// ...
})->middleware('role:editor,publisher');
Terminable Middleware
El middleware terminable es aquel que puede realizar cierto trabajo después de que la respuesta HTTP ha sido enviada al navegador. Si defines un método terminate en tu middleware y tu servidor web utiliza FastCGI, este método terminate será llamado automáticamente después de que la respuesta se haya enviado al navegador:
<?php
namespace Illuminate\Session\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class TerminatingMiddleware
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
return $next($request);
}
/**
* Handle tasks after the response has been sent to the browser.
*/
public function terminate(Request $request, Response $response): void
{
// ...
}
}
El método terminate debe recibir tanto la solicitud (Request) como la respuesta (Response). Una vez que hayas definido un middleware terminable, debes añadirlo a la lista de rutas o middleware globales en el archivo bootstrap/app.php de tu aplicación.
Cuando Laravel llama al método terminate en tu middleware, resolverá una nueva instancia fresca del middleware desde el contenedor de servicios. Si deseas utilizar la misma instancia del middleware cuando se llaman los métodos handle y terminate, debes registrar el middleware con el método singleton del contenedor. Normalmente, esto se hace en el método register de tu AppServiceProvider:
use App\Http\Middleware\TerminatingMiddleware;
/**
* Register any application services.
*/
public function register(): void
{
$this->app->singleton(TerminatingMiddleware::class);
}