> ## 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 — 라우트 보호 미들웨어 조사

> laravel/sentinel 패키지의 소스 코드를 읽어 봅니다. 드라이버 기반 라우트 보호 미들웨어로서, Telescope · Horizon · Pulse 등의 관리 도구를 어떻게 지키는지 설명합니다.

<Info>
  이 글은 소스 코드 조사(`1.x` 브랜치)에 기반한 정보입니다. 공식 문서는 아직 존재하지 않으며, 정식 릴리스 전의 패키지입니다 (2026년 4월 시점).
</Info>

## Sentinel이란

[Laravel Sentinel](https://github.com/laravel/sentinel)은 라우트로의 접근을 드라이버 기반으로 제어하는 보안 미들웨어 패키지입니다. Taylor Otwell과 Mior Muhammad Zaki에 의해 개발되었으며, PHP ^8.0 / Laravel 8\~13에 대응하고 있습니다.

주요 용도는 Telescope · Horizon · Pulse 같은 관리 도구 계열 라우트의 보호입니다. `SentinelMiddleware`를 라우트에 적용하는 것만으로, 드라이버가 정의한 인가 로직으로 접근을 제어할 수 있습니다.

```mermaid theme={null}
graph TD
    A["HTTP 요청"] --> B["SentinelMiddleware"]
    B --> C["Sentinel Facade"]
    C --> D["SentinelManager<br>(Illuminate\\Support\\Manager)"]
    D --> E["드라이버 해결<br>driverOrFallback()"]
    E --> F["Driver#authorize()"]
    F -->|true| G["다음 미들웨어로"]
    F -->|false| H["abort 401"]
```

## 기존 방법과의 비교

Telescope나 Horizon에는 각각 `gate`를 사용한 인가 구조가 준비되어 있었습니다.

```php theme={null}
// 기존 방법 (TelescopeServiceProvider 등)
Gate::before(function ($user) {
    return $user->isAdmin() ? true : null;
});
```

이 방식은 사용자의 인증 상태에 의존하고 있어, "로그인하지 않으면 판정할 수 없다"는 문제가 있었습니다. Sentinel은 미들웨어로 동작하므로, 인증의 유무와 관계없이 요청 단위로 접근을 제어할 수 있습니다. 또한 여러 관리 도구에 대해 한 곳에서 룰을 정의할 수 있습니다.

## 설치

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

`composer.json`의 `extra.laravel.providers`에 `SentinelServiceProvider`가 등록되어 있으므로, 서비스 프로바이더는 자동 감지됩니다. 추가 설정 파일 공개는 불필요합니다.

## 패키지의 아키텍처

```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 : 사용
```

## 기본 드라이버 (Laravel 드라이버)의 동작

기본 `Laravel` 드라이버는 **로컬 환경(`APP_ENV=local`)에서만** 제어를 수행합니다.

```php theme={null}
// src/Drivers/Laravel.php
public function authorize(Request $request): bool
{
    if (! $this->app()->environment('local')) {
        return true; // local 이외에는 항상 허가
    }

    // 터널링 서비스 경유의 접근에 경고
    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, ...'
        );
    }

    // Docker 로컬 환경은 허가
    if ($this->isRunningOnDockerLocally($request)) {
        return true;
    }

    // 리버스 프록시 경유의 접근을 확인
    return $this->authorizeAccessingViaReverseProxies($request);
}
```

흐름을 정리하면 다음과 같습니다.

```mermaid theme={null}
flowchart TD
    A[요청 수신] --> B{local 환경인가?}
    B -->|No| C[true 반환<br>(항상 허가)]
    B -->|Yes| D{프라이빗 IP이면서<br>trustedProxy 밖이면서<br>터널링 서비스인가?}
    D -->|Yes| E[RuntimeException 던짐<br>설정 안내 표시]
    D -->|No| F{Docker 로컬 환경인가?<br>127.0.0.1 + .dockerenv}
    F -->|Yes| G[true 반환<br>(허가)]
    F -->|No| H{비프라이빗 IP이면서<br>trustedProxy 경유인가?}
    H -->|Yes| I[false 반환<br>(차단)]
    H -->|No| J[true 반환<br>(허가)]
```

<Warning>
  `Laravel` 드라이버는 `local` 환경 이외에서는 항상 `true`(허가)를 반환합니다. 프로덕션 환경에서의 접근 제어가 필요한 경우에는 커스텀 드라이버를 작성해 주세요.
</Warning>

### 프라이빗 IP의 판정

`Driver` 기저 클래스의 `isPrivateIp()`는 Symfony의 `IpUtils`를 사용하여, 이하의 IP 범위를 프라이빗 IP로 판정합니다.

| 범위               | 설명              |
| ---------------- | --------------- |
| `127.0.0.0/8`    | 루프백 (RFC1700)   |
| `10.0.0.0/8`     | 프라이빗 (RFC1918)  |
| `192.168.0.0/16` | 프라이빗 (RFC1918)  |
| `172.16.0.0/12`  | 프라이빗 (RFC1918)  |
| `169.254.0.0/16` | 링크 로컬 (RFC3927) |
| `::1/128`        | IPv6 루프백        |
| `fc00::/7`       | IPv6 유니크 로컬     |
| `fe80::/10`      | IPv6 링크 로컬      |

### Docker 로컬 환경 검출

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

`REMOTE_ADDR`이 `127.0.0.1`이고, 프로젝트 루트에 `.dockerenv` 파일이 존재하는 경우를 Docker 로컬 환경으로 판정합니다.

## 미들웨어로서 사용하기

### 기본 사용법

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

### 드라이버 지정

미들웨어의 인자로 드라이버 이름을 전달할 수 있습니다. 존재하지 않는 드라이버 이름을 전달한 경우에는 기본 드라이버로 폴백합니다 (`driverOrFallback()`의 동작).

```php theme={null}
Route::middleware([SentinelMiddleware::class . ':custom-driver'])->group(function () {
    // 커스텀 드라이버로 인가
});
```

### 미들웨어의 구현

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

`authorize()`가 `false`를 반환하면 HTTP 401로 중단합니다. `Driver::authorizeOrFail()`을 사용하면 `AuthorizationException`을 던질 수도 있습니다 (미들웨어 자체는 이것을 사용하지 않습니다).

## 커스텀 드라이버 작성

`Driver` 추상 클래스를 상속하여 `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
        {
            // 인증되어 있고 admin 롤을 가진 사용자만 허가
            $user = $request->user();

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

`extend()`의 등록은 `AppServiceProvider::boot()` 등에서 수행합니다.

### 기저 클래스의 유틸리티 메서드 활용

`Driver` 클래스에는 IP 판정 등의 편리한 메서드가 준비되어 있으며, 커스텀 드라이버에서 자유롭게 이용할 수 있습니다.

```php theme={null}
public function authorize(Request $request): bool
{
    // 프로덕션 환경에서는 프라이빗 IP에서만 허가
    if ($this->app()->environment('production')) {
        return $this->isPrivateIp($request->ip());
    }

    // 로컬에서는 Docker나 직접 접근만
    return $this->isRunningOnDockerLocally($request)
        || $this->authorizeAccessingViaReverseProxies($request);
}
```

## SentinelManager의 구조

`SentinelManager`는 `Illuminate\Support\Manager`를 상속하고 있습니다. `Manager`는 드라이버 패턴을 구현하기 위한 Laravel의 기반 클래스로, `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  // 로그에 기록하지 않음
        );
    }
}
```

`driverOrFallback()`은 지정한 드라이버가 발견되지 않는 경우에도 기본 드라이버로 조용히 폴백하므로, 미들웨어 인자에 존재하지 않는 드라이버 이름을 전달해도 오류가 나지 않습니다.

`SentinelManager`는 `SentinelServiceProvider`에 의해 스코프 부여 싱글톤(`scoped`)으로 등록되므로, 1요청 내에서 드라이버의 인스턴스가 재이용됩니다.

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

## 정리

`laravel/sentinel`은 작은 패키지이지만, `Illuminate\Support\Manager`를 사용한 드라이버 패턴에 의해 높은 확장성을 가집니다.

| 항목         | 내용                          |
| ---------- | --------------------------- |
| 버전         | 1.x                         |
| 대응 PHP     | ^8.0                        |
| 대응 Laravel | 8\~13                       |
| 기본 드라이버    | local 환경만 제어                |
| 커스텀 드라이버   | `Sentinel::extend()`로 추가 가능 |

기본 `Laravel` 드라이버는 로컬 개발 시 터널링 서비스 경유의 접근을 방지하는 것에 특화되어 있으며, 프로덕션 환경의 보호에는 자체 커스텀 드라이버를 작성해야 합니다. 공식 문서가 정비되면 보다 구체적인 이용 패턴이 명확해질 것입니다.

<Card title="laravel/sentinel 리포지토리" icon="github" href="https://github.com/laravel/sentinel">
  소스 코드와 최신 변경은 GitHub의 `1.x` 브랜치에서 확인할 수 있습니다.
</Card>


## Related topics

- [미들웨어](/ko/middleware.md)
- [Webhook / Bot - LINE SDK for Laravel](/ko/packages/laravel-line-sdk/bot.md)
- [CSRF 보호](/ko/csrf.md)
- [인증 입문](/ko/authentication.md)
- [Laravel Sanctum (API 토큰 인증)](/ko/sanctum.md)
