> ## 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 Fortify와 스타터 킷

> 프론트엔드 비의존의 인증 백엔드 Laravel Fortify의 내부 설계를 해설합니다. 현행 스타터 킷에서 왜 Fortify가 사용되는지, FortifyServiceProvider의 설정, 2FA·패스키의 활성화까지 다룹니다.

## Fortify란

[Laravel Fortify](https://github.com/laravel/fortify)는, 프론트엔드에 의존하지 않는 인증 백엔드 구현입니다. 로그인·등록·비밀번호 재설정·메일 확인·2FA·패스키에 필요한 라우트와 컨트롤러를 모두 제공합니다.

<Info>
  Fortify는 UI를 가지지 않습니다. 스타터 킷이나 자체 UI에서 Fortify의 라우트로 요청을 보냄으로써 기능합니다.
</Info>

"프론트엔드 비의존"이라는 설계 사상이 중요합니다. Blade·Inertia(React/Vue/Svelte)·API 등, 어떤 프론트엔드 스택에서도 동일한 인증 백엔드를 재이용할 수 있습니다. 이것이 Fortify가 오래 계속 사용되고 있는 이유입니다.

## 역사: Jetstream에서 현행 스타터 킷까지

```mermaid theme={null}
timeline
    title Fortify 의 변천
    2020 Laravel 8 : Jetstream 과 동시에 등장
               : Fortify 는 Jetstream 의 뒷단으로 자동 설치
    2020 Laravel 8 : Breeze 등장(Fortify 없음)
               : 심플한 인증 스캐폴딩으로서 독립
    2025 스타터 킷 개편 : React/Vue 스타터 킷에 2FA 대응이 필요해짐
                         : Fortify 가 재채택 → 인증 전체가 Fortify 기반으로
    2026 패스키 대응 : Fortify 에 Passkeys 기능이 추가됨
```

### 왜 현행 스타터 킷에서도 Fortify가 사용되고 있는가

당초의 React/Vue 스타터 킷은 Fortify 없이 구현되어 있었습니다. 그러나 **이중 인증(2FA)에 대한 대응이 필요해졌을 때, Fortify가 그대로 채택된** 것으로, 인증 전체가 Fortify 기반으로 전환되었습니다.

Fortify는 "프론트엔드 비의존"으로서 설계되어 있기 때문에, Inertia를 사용하는 스타터 킷과의 궁합이 좋고, 현재도 계속 사용되고 있습니다.

<Info>
  Fortify는 현재, 가장 오래 계속 사용되고 있는 Laravel 공식 인증 패키지입니다. 2020년의 등장 이후, Jetstream → 현행 스타터 킷과 형태를 바꿔가면서 현역을 지속하고 있습니다.
</Info>

## 현행 스타터 킷의 구성

React/Vue 스타터 킷에서는 다음의 파일에서 Fortify가 설정되어 있습니다.

* `app/Providers/FortifyServiceProvider.php` — 뷰·액션·레이트 리밋의 등록
* `config/fortify.php` — 활성화할 기능과 인증 설정

### `config/fortify.php`의 주요 설정

```php theme={null}
use Laravel\Fortify\Features;

return [
    'guard'    => 'web',
    'username' => 'email',
    'home'     => '/dashboard',

    'limiters' => [
        'login'      => 'login',
        'two-factor' => 'two-factor',
    ],

    'features' => [
        Features::registration(),
        Features::resetPasswords(),
        Features::emailVerification(),
        Features::twoFactorAuthentication([
            'confirm'         => true,
            'confirmPassword' => true,
        ]),
    ],
];
```

스타터 킷에서는 패스키(`Features::passkeys()`)는 기본으로 활성화되어 있지 않습니다. 활성화하려면 `features` 배열에 추가합니다.

## `FortifyServiceProvider`의 설정

스타터 킷의 `FortifyServiceProvider`는 3가지 역할을 담당합니다.

### 1. 액션 설정

```php theme={null}
private function configureActions(): void
{
    Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
    Fortify::createUsersUsing(CreateNewUser::class);
}
```

`app/Actions/Fortify/`에 배치된 클래스에서 사용자 작성과 비밀번호 재설정 로직을 커스터마이즈합니다. 검증이나 해시 처리는 여기에 집약되어 있습니다.

### 2. 뷰 설정

```php theme={null}
private function configureViews(): void
{
    Fortify::loginView(fn (Request $request) => Inertia::render('auth/login', [
        'canResetPassword' => Features::enabled(Features::resetPasswords()),
        'canRegister'      => Features::enabled(Features::registration()),
        'status'           => $request->session()->get('status'),
    ]));

    Fortify::resetPasswordView(fn (Request $request) => Inertia::render('auth/reset-password', [
        'email' => $request->email,
        'token' => $request->route('token'),
    ]));

    Fortify::requestPasswordResetLinkView(
        fn (Request $request) => Inertia::render('auth/forgot-password', [
            'status' => $request->session()->get('status'),
        ])
    );

    Fortify::verifyEmailView(
        fn (Request $request) => Inertia::render('auth/verify-email', [
            'status' => $request->session()->get('status'),
        ])
    );

    Fortify::registerView(fn () => Inertia::render('auth/register'));

    Fortify::twoFactorChallengeView(fn () => Inertia::render('auth/two-factor-challenge'));

    Fortify::confirmPasswordView(fn () => Inertia::render('auth/confirm-password'));
}
```

각 뷰 메서드는 Inertia 컴포넌트를 반환하는 클로저를 받습니다. `Features::enabled()`로 기능 플래그를 확인하여 뷰에 전달하고 있는 것이 포인트입니다.

<Info>
  Blade 앱에서는 `Inertia::render(...)` 대신 `view('auth.login')`을 반환합니다. 프론트엔드를 바꿔도 `FortifyServiceProvider` 측만 수정하면 인증 로직은 바뀌지 않습니다.
</Info>

### 3. 레이트 리밋 설정

```php theme={null}
private function configureRateLimiting(): void
{
    RateLimiter::for('two-factor', function (Request $request) {
        return Limit::perMinute(5)->by($request->session()->get('login.id'));
    });

    RateLimiter::for('login', function (Request $request) {
        $throttleKey = Str::transliterate(
            Str::lower($request->input(Fortify::username())) . '|' . $request->ip()
        );

        return Limit::perMinute(5)->by($throttleKey);
    });
}
```

* `login` 리미터: 이메일 주소와 IP 주소의 조합으로 제한(브루트포스 대책)
* `two-factor` 리미터: 세션 내의 로그인 시도 ID로 제한

`config/fortify.php`의 `limiters` 키와 일치하는 키를 `RateLimiter::for()`에 등록합니다.

## 주요 기능과 등록되는 라우트

Fortify가 등록하는 주요 라우트를 기능별로 정리합니다.

| 기능           | 메서드                       | 라우트                                |
| ------------ | ------------------------- | ---------------------------------- |
| 로그인          | `GET`                     | `/login`                           |
| 로그인          | `POST`                    | `/login`                           |
| 로그아웃         | `POST`                    | `/logout`                          |
| 등록           | `GET`                     | `/register`                        |
| 등록           | `POST`                    | `/register`                        |
| 비밀번호 재설정 요청  | `GET` / `POST`            | `/forgot-password`                 |
| 비밀번호 재설정     | `GET` / `POST`            | `/reset-password`                  |
| 메일 확인        | `GET`                     | `/email/verify`                    |
| 메일 확인 링크 재전송 | `POST`                    | `/email/verification-notification` |
| 비밀번호 확인      | `GET` / `POST`            | `/user/confirm-password`           |
| 2FA 활성화      | `POST`                    | `/user/two-factor-authentication`  |
| 2FA 챌린지      | `GET` / `POST`            | `/two-factor-challenge`            |
| 패스키 로그인      | `GET` / `POST`            | `/passkeys/login`                  |
| 패스키 관리       | `GET` / `POST` / `DELETE` | `/user/passkeys`                   |

`php artisan route:list --name=fortify` 또는 단순히 `php artisan route:list`로 실제로 등록되어 있는 라우트를 확인할 수 있습니다.

## 이중 인증(2FA)

스타터 킷에서는 `Features::twoFactorAuthentication()`이 활성화되어 있습니다. `confirm`과 `confirmPassword`는 모두 `true`로 설정되어 있습니다.

| 옵션                | 의미                         |
| ----------------- | -------------------------- |
| `confirm`         | 활성화 후에 인증 코드에서의 확인을 요구함    |
| `confirmPassword` | 2FA의 설정 변경 전에 비밀번호 확인을 요구함 |

2FA의 설정 화면에서, 사용자는 이하의 조작을 할 수 있습니다.

<Steps>
  <Step title="2FA를 활성화한다">
    `/user/two-factor-authentication`에 POST 요청을 송신합니다. 성공하면 세션에 `two-factor-authentication-enabled` 상태가 세팅됩니다.
  </Step>

  <Step title="QR 코드를 확인한다">
    `/user/two-factor-qr-code`에 GET 요청을 보내면, 인증 앱에 읽어들이는 QR 코드의 SVG가 반환됩니다.
  </Step>

  <Step title="인증 코드로 확인한다">
    `/user/confirmed-two-factor-authentication`에 POST 요청으로 코드를 송신하고, 설정을 완료합니다.
  </Step>

  <Step title="리커버리 코드를 보관한다">
    `/user/two-factor-recovery-codes`에 GET 요청을 보내고, 리커버리 코드를 취득하여 안전한 장소에 보관합니다.
  </Step>
</Steps>

## 패스키

패스키는 Fortify의 기능으로서 최근 추가되었습니다. WebAuthn을 사용한 비밀번호 없는 인증으로, Face ID·Touch ID·Windows Hello·하드웨어 시큐리티 키에 대응하고 있습니다.

### 활성화

`config/fortify.php`의 `features` 배열에 추가합니다.

```php theme={null}
'features' => [
    Features::registration(),
    Features::resetPasswords(),
    Features::emailVerification(),
    Features::twoFactorAuthentication([
        'confirm'         => true,
        'confirmPassword' => true,
    ]),
    Features::passkeys([
        'confirmPassword' => true,
    ]),
],
```

다음으로, `User` 모델에 `PasskeyUser` 컨트랙트와 `PasskeyAuthenticatable` 트레이트를 추가합니다.

```php theme={null}
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Fortify\Contracts\PasskeyUser;
use Laravel\Fortify\PasskeyAuthenticatable;

class User extends Authenticatable implements PasskeyUser
{
    use PasskeyAuthenticatable;
}
```

WebAuthn 설정은 `config/fortify.php`의 `passkeys` 키에서 관리합니다.

```php theme={null}
'passkeys' => [
    'relying_party_id'  => parse_url(config('app.url'), PHP_URL_HOST),
    'allowed_origins'   => [config('app.url')],
    'user_handle_secret' => config('app.key'),
    'timeout'           => 60000,
],
```

<Info>
  Fortify의 패스키는 내부에서 `laravel/passkeys` Composer 패키지를 래핑하고 있습니다. `laravel/passkeys`의 설정 파일을 공개할 필요는 없고, `config/fortify.php`의 `passkeys` 키가 우선됩니다.
</Info>

패스키의 상세한 구현(`@laravel/passkeys` JS 클라이언트, React/Vue/Svelte 헬퍼 등)에 대해서는, [패스키 초기 조사](/ko/blog/passkeys-introduction)도 참조해 주세요.

## 관련 페이지

<Card title="커스텀 인증 가드" icon="shield" href="/ko/advanced/custom-auth-guard">
  Guard·StatefulGuard 인터페이스를 구현하여 커스텀 인증 가드를 만드는 방법을 해설합니다.
</Card>

<Card title="공식 문서: Laravel Fortify" icon="book-open" href="https://laravel.com/docs/fortify">
  설치·전체 기능·커스터마이즈의 상세는 공식 문서를 참조해 주세요.
</Card>


## Related topics

- [Laravel Maestro — 스타터 킷 개발의 오케스트레이터](/ko/blog/maestro-introduction.md)
- [Laravel 스타터 킷 만드는 법](/ko/advanced/starter-kit-creation.md)
- [스타터 킷](/ko/starter-kits.md)
- [laravel/ui에서 Fortify로의 마이그레이션 가이드](/ko/blog/ui-to-fortify.md)
- [Laravel Passkeys 초기 조사 (passkeys-server + @laravel/passkeys)](/ko/blog/passkeys-introduction.md)
