Middleware
Introducción
El middleware proporciona un mecanismo conveniente para inspeccionar y filtrar las solicitudes HTTP que ingresan a su aplicación. Por ejemplo, Laravel incluye un middleware que verifica que el usuario de su aplicación esté autenticado. Si el usuario no está autenticado, el middleware redirigirá al usuario a la pantalla de inicio de sesión de su aplicación. Sin embargo, si el usuario está autenticado, el middleware permitirá que la solicitud continúe en la aplicación.
Se pueden escribir middleware adicionales para realizar una variedad de tareas además de la autenticación. Por ejemplo, un middleware de registro podría registrar todas las solicitudes entrantes a su aplicación. Laravel incluye una variedad de middleware, incluidos middleware para autenticación y protección CSRF; sin embargo, todos los middleware definidos por el usuario generalmente se encuentran en el directorio app/Http/Middleware
de su aplicación.
Definiendo Middleware
Para crear un nuevo middleware, use el comando Artisan make:middleware
:
php artisan make:middleware EnsureTokenIsValid
Este comando colocará una nueva clase EnsureTokenIsValid
dentro de su directorio app/Http/Middleware
. En este middleware, solo permitiremos el acceso a la ruta si el token proporcionado coincide con un valor especificado. De lo contrario, redirigiremos a los usuarios de vuelta al URI /home
:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureTokenIsValid
{
/**
* Manejar una solicitud entrante.
*
* @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 puede 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 profundamente en la aplicación. Para pasar la solicitud más profundamente en la aplicación (permitiendo que el middleware «pase»), debe llamar al callback $next
con la solicitud $request
.
Es mejor imaginar el middleware como una serie de «capas» que las solicitudes HTTP deben atravesar antes de llegar a su aplicación. Cada capa puede examinar la solicitud e incluso rechazarla por completo.
Todos los middleware se resuelven a través del contenedor de servicios, por lo que puede insinuar cualquier dependencia que necesite dentro del constructor de un middleware.
Middleware y Respuestas
Por supuesto, un middleware puede realizar tareas antes o después de pasar la solicitud más profundamente en la aplicación. Por ejemplo, el siguiente middleware realizaría 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
{
// Realizar acción
return $next($request);
}
}
Sin embargo, este middleware realizaría 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);
// Realizar acción
return $response;
}
}
Registrando Middleware
Middleware Global
Si desea que un middleware se ejecute durante cada solicitud HTTP a su aplicación, puede agregarlo a la pila de middleware global en el archivo bootstrap/app.php
de su 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 su aplicación. El método append
agrega el middleware al final de la lista de middleware global. Si desea agregar un middleware al principio de la lista, debe usar el método prepend
.
Gestionar Manualmente el Middleware Global Predeterminado de Laravel
Si desea gestionar manualmente la pila de middleware global de Laravel, puede proporcionar la pila predeterminada de middleware global de Laravel al método use
. Luego, puede ajustar la pila de middleware predeterminada 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,
]);
})
Asignando Middleware a Rutas
Si desea asignar middleware a rutas específicas, puede invocar el método middleware
al definir la ruta:
use App\Http\Middleware\EnsureTokenIsValid;
Route::get('/profile', function () {
// ...
})->middleware(EnsureTokenIsValid::class);
Puede asignar múltiples middleware a la ruta pasando un array de nombres de middleware al método middleware
:
Route::get('/', function () {
// ...
})->middleware([First::class, Second::class]);
Excluyendo Middleware
Al asignar middleware a un grupo de rutas, ocasionalmente puede necesitar evitar que el middleware se aplique a una ruta individual dentro del grupo. Puede 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 puede excluir un conjunto dado de middleware de un grupo completo de definiciones de rutas:
use App\Http\Middleware\EnsureTokenIsValid;
Route::withoutMiddleware([EnsureTokenIsValid::class])->group(function () {
Route::get('/profile', function () {
// ...
});
});
El método withoutMiddleware
solo puede eliminar middleware de ruta y no se aplica al middleware global.
Grupos de Middleware
A veces puede querer agrupar varios middleware bajo una sola clave para hacerlos más fáciles de asignar a rutas. Puede lograr esto usando el método appendToGroup
dentro del archivo bootstrap/app.php
de su 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 controladores usando la misma sintaxis que el middleware individual:
Route::get('/', function () {
// ...
})->middleware('group-name');
Route::middleware(['group-name'])->group(function () {
// ...
});
Grupos de Middleware Predeterminados de Laravel
Laravel incluye grupos de middleware predefinidos web
y api
que contienen middleware comunes que puede querer aplicar a sus rutas web y API. Recuerde, Laravel aplica automáticamente estos grupos de middleware a los archivos routes/web.php
y routes/api.php
correspondientes:
El 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
El Grupo de Middleware api
- Illuminate\Routing\Middleware\SubstituteBindings
Si desea agregar o anteponer middleware a estos grupos, puede usar los métodos web
y api
dentro del archivo bootstrap/app.php
de su aplicación. Los métodos web
y api
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,
]);
})
Incluso puede reemplazar una de las entradas del grupo de middleware predeterminado de Laravel con un middleware personalizado propio:
use App\Http\Middleware\StartCustomSession;
use Illuminate\Session\Middleware\StartSession;
$middleware->web(replace: [
StartSession::class => StartCustomSession::class,
]);
O, puede eliminar un middleware por completo:
$middleware->web(remove: [
StartSession::class,
]);
Gestionar Manualmente los Grupos de Middleware Predeterminados de Laravel
Si desea gestionar manualmente todos los middleware dentro de los grupos de middleware web
y api
predeterminados de Laravel, puede redefinir los grupos por completo. El ejemplo a continuación definirá los grupos de middleware web
y api
con su middleware predeterminado, permitiéndole 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 routes/web.php
y routes/api.php
correspondientes de su aplicación por el archivo bootstrap/app.php
.
Aliases de Middleware
Puede asignar aliases a middleware en el archivo bootstrap/app.php
de su aplicación. Los aliases de middleware le permiten definir un alias corto para una clase de middleware dada, lo cual puede ser 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 el alias del middleware ha sido definido en el archivo bootstrap/app.php
de su aplicación, puede 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 aliases por defecto. Por ejemplo, el middleware auth
es un alias para el middleware Illuminate\Auth\Middleware\Authenticate
. A continuación se muestra una lista de los aliases 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 o Illuminate\Routing\Middleware\ThrottleRequestsWithRedis |
verified | Illuminate\Auth\Middleware\EnsureEmailIsVerified |
Ordenando Middleware
Raramente, puede necesitar que su middleware se ejecute en un orden específico pero no tener control sobre su orden cuando se asignan a la ruta. En estas situaciones, puede especificar la prioridad de su middleware usando el método priority
en el archivo bootstrap/app.php
de su 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,
]);
})
Parámetros de Middleware
El middleware también puede recibir parámetros adicionales. Por ejemplo, si su aplicación necesita verificar que el usuario autenticado tenga un «rol» dado antes de realizar una acción, podría crear un middleware EnsureUserHasRole
que reciba un nombre de rol como argumento adicional.
Los parámetros adicionales del 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
{
/**
* Manejar una solicitud entrante.
*
* @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)) {
// Redirigir...
}
return $next($request);
}
}
Los parámetros del middleware pueden especificarse al definir la ruta separando el nombre del middleware y los parámetros con un :
use App\Http\Middleware\EnsureUserHasRole;
Route::put('/post/{id}', function (string $id) {
// ...
})->middleware(EnsureUserHasRole::class.':editor');
Se pueden delimitar múltiples parámetros con comas:
Route::put('/post/{id}', function (string $id) {
// ...
})->middleware(EnsureUserHasRole::class.':editor,publisher');
Middleware Terminable
A veces un middleware puede necesitar hacer algún trabajo después de que la respuesta HTTP haya sido enviada al navegador. Si define un método terminate
en su middleware y su servidor web está usando FastCGI, el método terminate
se llamará automáticamente después de que la respuesta sea enviada al navegador:
<?php
namespace Illuminate\Session\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class TerminatingMiddleware
{
/**
* Manejar una solicitud entrante.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
return $next($request);
}
/**
* Manejar tareas después de que la respuesta haya sido enviada al navegador.
*/
public function terminate(Request $request, Response $response): void
{
// ...
}
}
El método terminate
debe recibir tanto la solicitud como la respuesta. Una vez que haya definido un middleware terminable, debe agregarlo a la lista de rutas o middleware global en el archivo bootstrap/app.php
de su aplicación.
Al llamar al método terminate
en su middleware, Laravel resolverá una nueva instancia del middleware desde el contenedor de servicios. Si desea usar la misma instancia de middleware cuando se llamen los métodos handle
y terminate
, registre el middleware con el contenedor usando el método singleton
del contenedor. Típicamente, esto debería hacerse en el método register
de su AppServiceProvider
:
use App\Http\Middleware\TerminatingMiddleware;
/**
* Registrar cualquier servicio de aplicación.
*/
public function register(): void
{
$this->app->singleton(TerminatingMiddleware::class);
}