> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Laravel Sentinel — Investigación del middleware de protección de rutas

> Análisis del código fuente del paquete laravel/sentinel. Se explica cómo, como middleware de protección de rutas basado en drivers, protege herramientas de administración como Telescope, Horizon y Pulse.

<Info>
  Este artículo se basa en la investigación del código fuente (rama `1.x`). Todavía no existe documentación oficial y es un paquete previo al lanzamiento (a fecha de abril de 2026).
</Info>

## ¿Qué es Sentinel?

[Laravel Sentinel](https://github.com/laravel/sentinel) es un paquete de middleware de seguridad que controla el acceso a las rutas mediante drivers. Está desarrollado por Taylor Otwell y Mior Muhammad Zaki, y es compatible con PHP ^8.0 / Laravel 8–13.

Su uso principal es proteger rutas de herramientas de administración como Telescope, Horizon o Pulse. Basta con aplicar `SentinelMiddleware` a la ruta para controlar el acceso con la lógica de autorización definida por el driver.

```mermaid theme={null}
graph TD
    A["Petición HTTP"] --> B["SentinelMiddleware"]
    B --> C["Facade Sentinel"]
    C --> D["SentinelManager<br>(Illuminate\\Support\\Manager)"]
    D --> E["Resolver driver<br>driverOrFallback()"]
    E --> F["Driver#authorize()"]
    F -->|true| G["Al siguiente middleware"]
    F -->|false| H["abort 401"]
```

## Comparación con los métodos tradicionales

Telescope y Horizon disponían cada uno de mecanismos de autorización basados en `gate`.

```php theme={null}
// Método tradicional (TelescopeServiceProvider, etc.)
Gate::before(function ($user) {
    return $user->isAdmin() ? true : null;
});
```

Este método dependía del estado de autenticación del usuario, y tenía el problema de "no se puede juzgar si no se ha iniciado sesión". Como Sentinel actúa como middleware, puede controlar el acceso por petición independientemente de si hay autenticación o no. Además, permite definir las reglas en un único lugar para múltiples herramientas de administración.

## Instalación

```bash theme={null}
composer require laravel/sentinel
```

Como en `composer.json`, dentro de `extra.laravel.providers`, está registrado el `SentinelServiceProvider`, el service provider se detecta automáticamente. No hace falta publicar archivos de configuración adicionales.

## Arquitectura del paquete

```mermaid theme={null}
classDiagram
    class Sentinel {
        +static getFacadeAccessor()
    }
    class SentinelManager {
        +createLaravelDriver() Laravel
        +getDefaultDriver() string
        +driverOrFallback(?string driver) mixed
        +extend(string driver, Closure callback) SentinelManager
    }
    class Driver {
        #Closure applicationResolver
        +authorize(Request request) bool*
        +authorizeOrFail(Request request) void
        #authorizeAccessingViaReverseProxies(Request request) bool
        #isRunningOnDockerLocally(Request request) bool
        #isPrivateIp(string requestIp) bool
        #app() Application
    }
    class Laravel {
        +authorize(Request request) bool
    }
    class SentinelMiddleware {
        +handle(Request request, Closure next, ?string driver) mixed
    }

    Sentinel --> SentinelManager : Facade
    SentinelManager --|> Manager
    Laravel --|> Driver
    SentinelMiddleware --> Sentinel : usa
```

## Comportamiento del driver por defecto (driver Laravel)

El driver `Laravel` por defecto controla el acceso **solo en el entorno local (`APP_ENV=local`)**.

```php theme={null}
// src/Drivers/Laravel.php
public function authorize(Request $request): bool
{
    if (! $this->app()->environment('local')) {
        return true; // fuera de local siempre permite
    }

    // Advierte sobre accesos vía servicios de tunneling
    if ($this->isPrivateIp($request->ip())
        && ! $request->isFromTrustedProxy()
        && Str::endsWith($request->host(), ['.sharedwithexpose.com', '.ngrok-free.app', '.ngrok.io'])) {
        throw new RuntimeException(
            'Unable to access "..." using "local" environment, ...'
        );
    }

    // Permite el entorno Docker local
    if ($this->isRunningOnDockerLocally($request)) {
        return true;
    }

    // Comprueba el acceso a través de reverse proxies
    return $this->authorizeAccessingViaReverseProxies($request);
}
```

Resumen del flujo:

```mermaid theme={null}
flowchart TD
    A[Recepción de la petición] --> B{¿Entorno local?}
    B -->|No| C[Devuelve true<br>(permite siempre)]
    B -->|Sí| D{¿IP privada y<br>fuera de trustedProxy y<br>servicio de tunneling?}
    D -->|Sí| E[Lanza RuntimeException<br>Muestra la guía de configuración]
    D -->|No| F{¿Entorno Docker local?<br>127.0.0.1 + .dockerenv}
    F -->|Sí| G[Devuelve true<br>(permite)]
    F -->|No| H{¿IP no privada<br>vía trustedProxy?}
    H -->|Sí| I[Devuelve false<br>(bloquea)]
    H -->|No| J[Devuelve true<br>(permite)]
```

<Warning>
  El driver `Laravel` devuelve siempre `true` (permite) fuera del entorno `local`. Si necesitas control de acceso en producción, crea un driver personalizado.
</Warning>

### Detección de IP privada

`isPrivateIp()` de la clase base `Driver` usa `IpUtils` de Symfony y considera privadas las siguientes rangos.

| Rango            | Descripción          |
| ---------------- | -------------------- |
| `127.0.0.0/8`    | Loopback (RFC1700)   |
| `10.0.0.0/8`     | Privado (RFC1918)    |
| `192.168.0.0/16` | Privado (RFC1918)    |
| `172.16.0.0/12`  | Privado (RFC1918)    |
| `169.254.0.0/16` | Link-local (RFC3927) |
| `::1/128`        | Loopback IPv6        |
| `fc00::/7`       | Unique local IPv6    |
| `fe80::/10`      | Link-local IPv6      |

### Detección del entorno Docker local

```php theme={null}
// src/Drivers/Driver.php
protected function isRunningOnDockerLocally(Request $request): bool
{
    return $request->server->get('REMOTE_ADDR') === '127.0.0.1'
        && file_exists(base_path('.dockerenv'));
}
```

Cuando `REMOTE_ADDR` es `127.0.0.1` y existe el archivo `.dockerenv` en la raíz del proyecto, se considera entorno Docker local.

## Uso como middleware

### Uso básico

```php theme={null}
use Laravel\Sentinel\Http\Middleware\SentinelMiddleware;

Route::middleware(SentinelMiddleware::class)->group(function () {
    Route::get('/telescope', function () { /* ... */ });
    Route::get('/horizon', function () { /* ... */ });
    Route::get('/pulse', function () { /* ... */ });
});
```

### Especificar el driver

Puedes pasar el nombre del driver como argumento del middleware. Si pasas un nombre de driver que no existe, cae al driver por defecto (comportamiento de `driverOrFallback()`).

```php theme={null}
Route::middleware([SentinelMiddleware::class . ':custom-driver'])->group(function () {
    // Autoriza con el driver personalizado
});
```

### Implementación del middleware

```php theme={null}
// src/Http/Middleware/SentinelMiddleware.php
public function handle(Request $request, Closure $next, ?string $driver = null)
{
    abort_unless(Sentinel::driverOrFallback($driver)->authorize($request), 401);

    return $next($request);
}
```

Si `authorize()` devuelve `false`, se interrumpe con HTTP 401. Usando `Driver::authorizeOrFail()` también se puede lanzar `AuthorizationException` (el middleware en sí no lo usa).

## Crear un driver personalizado

Puedes crear un driver personalizado heredando la clase abstracta `Driver` e implementando el método `authorize()`.

```php theme={null}
use Laravel\Sentinel\Sentinel;
use Laravel\Sentinel\Drivers\Driver;
use Illuminate\Http\Request;

Sentinel::extend('admin-only', function ($app) {
    return new class extends Driver {
        public function authorize(Request $request): bool
        {
            // Solo permite usuarios autenticados con rol admin
            $user = $request->user();

            return $user !== null && $user->hasRole('admin');
        }
    };
});
```

El registro con `extend()` se hace en `AppServiceProvider::boot()`, por ejemplo.

### Aprovechar los métodos utilitarios de la clase base

La clase `Driver` ofrece métodos útiles como la detección de IP, que se pueden usar libremente desde un driver personalizado.

```php theme={null}
public function authorize(Request $request): bool
{
    // En producción, permite solo desde IPs privadas
    if ($this->app()->environment('production')) {
        return $this->isPrivateIp($request->ip());
    }

    // En local, solo Docker o acceso directo
    return $this->isRunningOnDockerLocally($request)
        || $this->authorizeAccessingViaReverseProxies($request);
}
```

## Cómo funciona SentinelManager

`SentinelManager` hereda de `Illuminate\Support\Manager`. `Manager` es la clase base de Laravel para implementar el patrón driver, que resuelve la instancia con `driver()` cacheándola.

```php theme={null}
// src/SentinelManager.php
class SentinelManager extends Manager
{
    protected function createLaravelDriver()
    {
        return new Laravel(fn () => $this->getContainer());
    }

    public function getDefaultDriver()
    {
        return 'laravel';
    }

    public function driverOrFallback(?string $driver)
    {
        return rescue(
            fn () => $this->driver($driver),
            value(fn () => $this->driver()),
            false  // no registrar en log
        );
    }
}
```

`driverOrFallback()` cae silenciosamente al driver por defecto aunque no se encuentre el driver indicado, por lo que pasar al middleware un nombre de driver inexistente no produce error.

`SentinelManager` se registra como singleton con scope (`scoped`) desde el `SentinelServiceProvider`, por lo que la instancia del driver se reutiliza dentro de una misma petición.

```php theme={null}
// src/SentinelServiceProvider.php
public function register(): void
{
    $this->app->scoped(SentinelManager::class, fn ($app) => new SentinelManager($app));
}
```

## Conclusión

`laravel/sentinel` es un paquete pequeño, pero con alta extensibilidad gracias al patrón driver basado en `Illuminate\Support\Manager`.

| Elemento             | Contenido                         |
| -------------------- | --------------------------------- |
| Versión              | 1.x                               |
| PHP compatible       | ^8.0                              |
| Laravel compatible   | 8–13                              |
| Driver por defecto   | Solo controla en entorno local    |
| Driver personalizado | Se añade con `Sentinel::extend()` |

El driver `Laravel` por defecto se especializa en prevenir accesos vía servicios de tunneling durante el desarrollo local, y para proteger producción hay que crear un driver personalizado propio. Cuando se publique la documentación oficial, se aclararán patrones de uso más concretos.

<Card title="Repositorio laravel/sentinel" icon="github" href="https://github.com/laravel/sentinel">
  Puedes ver el código fuente y los cambios más recientes en la rama `1.x` de GitHub.
</Card>


## Related topics

- [Protección CSRF](/es/csrf.md)
- [Enrutamiento](/es/routing.md)
- [Middleware](/es/middleware.md)
- [Investigación inicial de Laravel Passkeys (passkeys-server + @laravel/passkeys)](/es/blog/passkeys-introduction.md)
- [Laravel Cashier (Stripe)](/es/cashier.md)
