> ## 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 — route protection middleware research

> A source-code walkthrough of the laravel/sentinel package. Learn how this driver-based route protection middleware guards administrative tools like Telescope, Horizon, and Pulse.

<Info>
  This article is based on source-code research (the `1.x` branch). There are no official docs yet, and the package is pre-release (as of April 2026).
</Info>

## What is Sentinel?

[Laravel Sentinel](https://github.com/laravel/sentinel) is a security middleware package that controls access to routes using a driver-based approach. It's developed by Taylor Otwell and Mior Muhammad Zaki and supports PHP ^8.0 / Laravel 8–13.

Its primary use is protecting administrative routes like Telescope, Horizon, and Pulse. Just apply `SentinelMiddleware` to routes and access is controlled by the authorization logic defined in the driver.

```mermaid theme={null}
graph TD
    A["HTTP request"] --> B["SentinelMiddleware"]
    B --> C["Sentinel facade"]
    C --> D["SentinelManager<br>(Illuminate\\Support\\Manager)"]
    D --> E["Resolve driver<br>driverOrFallback()"]
    E --> F["Driver#authorize()"]
    F -->|true| G["Continue to next middleware"]
    F -->|false| H["abort 401"]
```

## Compared to the traditional approach

Telescope and Horizon each had their own `gate`-based authorization mechanism.

```php theme={null}
// Traditional approach (e.g. in TelescopeServiceProvider)
Gate::before(function ($user) {
    return $user->isAdmin() ? true : null;
});
```

That method depends on the user's authentication state and has the problem of "you can't decide unless someone is logged in." Because Sentinel works as middleware, it controls access per request regardless of authentication. It also lets you define rules for multiple admin tools in one place.

## Installation

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

Because `SentinelServiceProvider` is registered in `composer.json`'s `extra.laravel.providers`, the service provider is auto-detected. No config file publishing is needed.

## Package architecture

```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 : uses
```

## Default driver (the Laravel driver) behavior

The default `Laravel` driver applies controls **only in the local environment (`APP_ENV=local`)**.

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

    // Warn when accessed via tunneling services
    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, ...'
        );
    }

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

    // Check access via reverse proxies
    return $this->authorizeAccessingViaReverseProxies($request);
}
```

The flow summarized:

```mermaid theme={null}
flowchart TD
    A[Receive request] --> B{local env?}
    B -->|No| C[Return true<br>(always allow)]
    B -->|Yes| D{Private IP,<br>not a trusted proxy,<br>and a tunneling service?}
    D -->|Yes| E[Throw RuntimeException<br>with setup guidance]
    D -->|No| F{Docker local?<br>127.0.0.1 + .dockerenv}
    F -->|Yes| G[Return true<br>(allow)]
    F -->|No| H{Non-private IP<br>via a trusted proxy?}
    H -->|Yes| I[Return false<br>(block)]
    H -->|No| J[Return true<br>(allow)]
```

<Warning>
  The `Laravel` driver always returns `true` (allow) outside the `local` environment. If you need access control in production, create a custom driver.
</Warning>

### Private IP detection

The base `Driver` class's `isPrivateIp()` uses Symfony's `IpUtils` and treats the following IP ranges as private.

| Range            | Description          |
| ---------------- | -------------------- |
| `127.0.0.0/8`    | Loopback (RFC1700)   |
| `10.0.0.0/8`     | Private (RFC1918)    |
| `192.168.0.0/16` | Private (RFC1918)    |
| `172.16.0.0/12`  | Private (RFC1918)    |
| `169.254.0.0/16` | Link-local (RFC3927) |
| `::1/128`        | IPv6 loopback        |
| `fc00::/7`       | IPv6 unique local    |
| `fe80::/10`      | IPv6 link-local      |

### Docker local detection

```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'));
}
```

If `REMOTE_ADDR` is `127.0.0.1` and a `.dockerenv` file exists at the project root, the environment is judged to be Docker local.

## Using it as middleware

### Basic usage

```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 () { /* ... */ });
});
```

### Specifying a driver

You can pass a driver name as an argument to the middleware. If a non-existent driver name is passed, it falls back to the default driver (via `driverOrFallback()`).

```php theme={null}
Route::middleware([SentinelMiddleware::class . ':custom-driver'])->group(function () {
    // Authorized by the custom driver
});
```

### Middleware implementation

```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);
}
```

If `authorize()` returns `false`, it aborts with HTTP 401. You can also throw an `AuthorizationException` via `Driver::authorizeOrFail()` (though the middleware itself does not use this).

## Creating a custom driver

You can create a custom driver by extending the `Driver` abstract class and implementing `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
        {
            // Allow only authenticated users with the admin role
            $user = $request->user();

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

Register via `extend()` in `AppServiceProvider::boot()`, for example.

### Using base class utility methods

The `Driver` class provides helpful utilities like IP detection that custom drivers can freely use.

```php theme={null}
public function authorize(Request $request): bool
{
    // In production, allow only private IPs
    if ($this->app()->environment('production')) {
        return $this->isPrivateIp($request->ip());
    }

    // In local, only Docker or direct access
    return $this->isRunningOnDockerLocally($request)
        || $this->authorizeAccessingViaReverseProxies($request);
}
```

## How SentinelManager works

`SentinelManager` extends `Illuminate\Support\Manager`. `Manager` is Laravel's base class for implementing the driver pattern; it caches driver instances via `driver()`.

```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  // Don't log
        );
    }
}
```

`driverOrFallback()` silently falls back to the default driver if the specified driver is missing, so passing a non-existent driver name to the middleware argument doesn't error out.

Because `SentinelManager` is registered as a scoped singleton (`scoped`) by `SentinelServiceProvider`, the driver instance is reused within a single request.

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

## Summary

`laravel/sentinel` is a small package, but its driver pattern via `Illuminate\Support\Manager` gives it high extensibility.

| Item           | Details                            |
| -------------- | ---------------------------------- |
| Version        | 1.x                                |
| PHP            | ^8.0                               |
| Laravel        | 8–13                               |
| Default driver | Controls only in local environment |
| Custom drivers | Add via `Sentinel::extend()`       |

The default `Laravel` driver is specialized in preventing access via tunneling services during local development. To protect production, you need to create your own custom driver. When official documentation lands, more concrete usage patterns should become clear.

<Card title="laravel/sentinel repository" icon="github" href="https://github.com/laravel/sentinel">
  See the source and latest changes on the GitHub `1.x` branch.
</Card>


## Related topics

- [CSRF protection](/en/csrf.md)
- [Middleware](/en/middleware.md)
- [Routing](/en/routing.md)
- [Upgrade guide: Laravel 12 to 13](/en/blog/upgrade-12-to-13.md)
- [Authentication introduction](/en/authentication.md)
