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

# CSRF 보호

> CSRF 공격의 구조와 Laravel 13의 Origin 검증·토큰 검증에 의한 방어 방법을 설명합니다.

## 소개

CSRF(Cross-Site Request Forgery)는 로그인된 사용자를 사칭하여 의도하지 않은 요청을 보내게 하는 공격입니다.

예를 들어, 여러분의 애플리케이션에 `POST /user/email`이 있어 이메일 주소 변경을 받고 있다고 가정해 봅시다. 공격자가 다른 사이트에서 이 URL로 자동 전송하는 폼을 심어 두면, 사용자가 알아차리지 못하는 사이에 이메일 주소가 변경될 가능성이 있습니다.

Laravel 13에서는 `web` 미들웨어 그룹에 포함된 구조 덕분에 CSRF 보호가 기본적으로 활성화되어 있습니다.

## CSRF 공격 방지

Laravel의 `PreventRequestForgery` 미들웨어는 다음 2단계로 CSRF를 방지합니다.

1. **Origin 검증**(`Sec-Fetch-Site` 헤더)
2. **토큰 검증**(세션 단위의 CSRF 토큰)

먼저 Origin을 확인하고, 판정할 수 없거나 실패한 경우에는 토큰 검증으로 폴백합니다.

## Origin 검증

Laravel은 먼저 `Sec-Fetch-Site`를 확인하여 동일 Origin에서의 요청인지 판정합니다. 이는 HTTPS 환경에서 특히 효과적입니다.

Origin 검증을 통과하면 그 시점에서 요청은 허용됩니다. 통과하지 않는 경우에는 기존과 동일하게 CSRF 토큰 검증이 실행됩니다.

<Warning>
  `Sec-Fetch-Site`는 HTTPS 연결에서 사용되는 것이 전제입니다. HTTP 환경에서는 Origin 검증이 동작하지 않으며, 토큰 검증이 주요 방어 수단이 됩니다.
</Warning>

### Origin-only 모드

토큰 검증으로의 폴백을 무효화하고, Origin 검증만으로 판정할 수도 있습니다.

```php theme={null}
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withMiddleware(function (Middleware $middleware): void {
        $middleware->preventRequestForgery(originOnly: true);
    });
```

Origin-only 모드에서는 Origin 검증에 실패한 요청은 `419`가 아닌 `403`을 반환합니다.

서브도메인 간 요청 등에서 same-site를 허용하고 싶은 경우에는 `allowSameSite`를 설정합니다.

```php theme={null}
$middleware->preventRequestForgery(allowSameSite: true);
```

## 토큰 검증

Laravel은 세션마다 CSRF 토큰을 생성합니다. `csrf_token()` 또는 세션에서 가져올 수 있습니다.

```php theme={null}
use Illuminate\Http\Request;

Route::get('/token', function (Request $request) {
    $tokenFromSession = $request->session()->token();
    $tokenFromHelper = csrf_token();
});
```

`web` 라우트에서 `POST`·`PUT`·`PATCH`·`DELETE` 폼을 만드는 경우에는 반드시 `@csrf`를 포함시켜 주세요.

```blade theme={null}
<form method="POST" action="/profile">
    @csrf

    <!-- 동등한 hidden input -->
    <input type="hidden" name="_token" value="{{ csrf_token() }}" />
</form>
```

## URI 제외

Stripe Webhook처럼 외부 서비스에서 전송되는 요청에서는 특정 URI를 CSRF 보호에서 제외하는 경우가 있습니다.

```php theme={null}
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withMiddleware(function (Middleware $middleware): void {
        $middleware->preventRequestForgery(except: [
            'stripe/*',
            'http://example.com/foo/bar',
            'http://example.com/foo/*',
        ]);
    });
```

<Info>
  가능하면 Webhook 라우트는 `web` 미들웨어 그룹 외부에 배치하고, 제외 설정은 최소한으로 유지해 주세요.
</Info>

## X-CSRF-TOKEN 헤더

Laravel은 폼의 `_token`뿐만 아니라 `X-CSRF-TOKEN` 헤더도 검증합니다.

먼저 토큰을 meta 태그에 출력합니다.

```blade theme={null}
<meta name="csrf-token" content="{{ csrf_token() }}">
```

그 값을 AJAX 요청 헤더에 부여합니다.

```js theme={null}
$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': document
            .querySelector('meta[name="csrf-token"]')
            .getAttribute('content'),
    },
});
```

## X-XSRF-TOKEN 헤더

Laravel은 암호화된 `XSRF-TOKEN` 쿠키도 전송합니다. Axios나 Angular는 동일 Origin 요청 시 이 값을 `X-XSRF-TOKEN` 헤더에 자동으로 설정할 수 있습니다.

따라서 SPA나 AJAX 구현에서는 헤더 설정을 수동으로 작성하지 않아도 CSRF 대책이 활성화되는 경우가 있습니다.

## SPA에서의 고려 사항

SPA에서 Laravel을 API 백엔드로 사용하는 경우, 먼저 CSRF 쿠키를 획득한 후 로그인 요청을 보냅니다.

```js theme={null}
await axios.get('/sanctum/csrf-cookie');
await axios.post('/login', {
    email: 'user@example.com',
    password: 'password',
});
```

자세한 내용은 [Sanctum](/ko/sanctum)을 참조해 주세요.


## Related topics

- [Blade 템플릿](/ko/blade.md)
- [Laravel Fetch Metadata](/ko/packages/laravel-fetch-metadata.md)
- [Laravel 13 신기능 정리](/ko/blog/laravel-13-new-features.md)
- [미들웨어](/ko/middleware.md)
- [HTTP 클라이언트](/ko/http-client.md)
