메인 콘텐츠로 건너뛰기

시작하기

비밀번호 재설정은 웹 애플리케이션에 빠질 수 없는 인증 플로우입니다. 스타터 킷을 사용하면 이 기능은 자동으로 구축되지만, API 전용 프로젝트나 자체 UI를 구축하는 프로젝트에서는 수동 구현이 필요합니다. 수동 구현이 필요한 경우:
  • API 전용 백엔드(프론트엔드가 SPA·모바일 앱)
  • 스타터 킷을 사용하지 않고 인증 UI를 직접 구축하는 경우
  • 메일 알림이나 재설정 URL의 디자인을 완전히 커스터마이즈하고 싶은 경우
스타터 킷(laravel new)으로 프로젝트를 생성한 경우, 비밀번호 재설정 기능은 이미 구현되어 있습니다. 이 페이지에서는 스타터 킷 없이 구현하는 방법을 설명합니다.
비밀번호 재설정의 전체 플로우는 다음과 같습니다.

설정

비밀번호 재설정 설정은 config/auth.phppasswords 키에서 관리합니다.
// config/auth.php

'passwords' => [
    'users' => [
        'driver' => 'database',
        'provider' => 'users',
        'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
        'expire' => 60,
        'throttle' => 60,
    ],
],
  • driver — 데이터 저장 방식(database 또는 cache)
  • expire — 토큰의 유효 기간(분). 기본값은 60분
  • throttle — 재전송을 제한할 때까지의 대기 시간(초)

드라이버

database 드라이버

기본 드라이버입니다. 비밀번호 재설정 토큰을 데이터베이스의 password_reset_tokens 테이블에 저장합니다. 이 테이블은 Laravel의 기본 마이그레이션(0001_01_01_000000_create_users_table.php)에 포함되어 있습니다.
'passwords' => [
    'users' => [
        'driver' => 'database',
        'provider' => 'users',
        'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
        'expire' => 60,
        'throttle' => 60,
    ],
],

cache 드라이버

Laravel 11 이상에서 사용 가능한 새로운 옵션입니다. 데이터베이스 테이블이 필요 없기 때문에 간단한 구성으로 비밀번호 재설정을 구현할 수 있습니다.
cache 드라이버는 캐시 스토어에 토큰을 저장합니다. password_reset_tokens 테이블 마이그레이션이 필요 없게 됩니다. 토큰은 사용자의 이메일 주소를 키로 저장되므로, 앱 내 다른 위치에서 이메일 주소를 캐시 키로 사용하지 않도록 주의하세요.
'passwords' => [
    'users' => [
        'driver' => 'cache',
        'provider' => 'users',
        'store' => 'passwords', // 옵션: 전용 캐시 스토어
        'expire' => 60,
        'throttle' => 60,
    ],
],
store 키에 전용 캐시 스토어를 지정하면 php artisan cache:clear로 재설정 데이터가 지워지는 것을 방지할 수 있습니다. 지정하는 값은 config/cache.php에 설정된 스토어 이름에 대응시킵니다.

모델 준비

비밀번호 재설정 기능을 사용하려면 App\Models\User 모델에 두 개의 트레이트가 필요합니다.
<?php

namespace App\Models;

use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable, CanResetPassword;

    // ...
}
  • Notifiable — 메일 알림을 전송하기 위해 필요
  • CanResetPassword — 비밀번호 재설정 토큰의 생성·검증에 필요한 메서드를 제공
Laravel의 기본 User 모델에는 이미 이 트레이트들이 포함되어 있습니다. 신규 설치의 경우 추가할 필요가 없습니다.

라우팅 구현

비밀번호 재설정에는 네 개의 라우트가 필요합니다.

1. 재설정 링크 요청 폼

이메일 주소를 입력하는 폼을 표시합니다.
// routes/web.php

Route::get('/forgot-password', function () {
    return view('auth.forgot-password');
})->middleware('guest')->name('password.request');
해당하는 Blade 뷰:
{{-- resources/views/auth/forgot-password.blade.php --}}

<form method="POST" action="/forgot-password">
    @csrf

    <div>
        <label for="email">이메일 주소</label>
        <input id="email" type="email" name="email" value="{{ old('email') }}" required autofocus>
        @error('email')
            <span>{{ $message }}</span>
        @enderror
    </div>

    @if (session('status'))
        <div>{{ session('status') }}</div>
    @endif

    <button type="submit">재설정 링크 전송</button>
</form>

2. 재설정 링크 전송 처리

폼 제출을 받아 Password::sendResetLink()로 재설정 메일을 전송합니다.
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;

Route::post('/forgot-password', function (Request $request) {
    $request->validate(['email' => 'required|email']);

    $status = Password::sendResetLink(
        $request->only('email')
    );

    return $status === Password::ResetLinkSent
        ? back()->with(['status' => __($status)])
        : back()->withErrors(['email' => __($status)]);
})->middleware('guest')->name('password.email');
Password::sendResetLink()는 상태 문자열을 반환합니다.
상태 상수설명
Password::ResetLinkSent재설정 링크를 전송함
Password::INVALID_USER지정한 이메일의 사용자를 찾을 수 없음
Password::RESET_THROTTLED전송이 제한됨(스로틀링)

3. 비밀번호 재설정 폼

메일 안의 링크를 클릭한 사용자에게 새 비밀번호를 입력하게 하는 폼을 표시합니다.
Route::get('/reset-password/{token}', function (string $token) {
    return view('auth.reset-password', ['token' => $token]);
})->middleware('guest')->name('password.reset');
해당하는 Blade 뷰:
{{-- resources/views/auth/reset-password.blade.php --}}

<form method="POST" action="/reset-password">
    @csrf

    <input type="hidden" name="token" value="{{ $token }}">

    <div>
        <label for="email">이메일 주소</label>
        <input id="email" type="email" name="email" value="{{ old('email') }}" required autofocus>
        @error('email')
            <span>{{ $message }}</span>
        @enderror
    </div>

    <div>
        <label for="password">새 비밀번호</label>
        <input id="password" type="password" name="password" required>
        @error('password')
            <span>{{ $message }}</span>
        @enderror
    </div>

    <div>
        <label for="password_confirmation">비밀번호 확인</label>
        <input id="password_confirmation" type="password" name="password_confirmation" required>
    </div>

    <button type="submit">비밀번호 재설정</button>
</form>

4. 비밀번호 재설정 실행 처리

폼을 받아 Password::reset()으로 비밀번호를 갱신합니다.
use App\Models\User;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;

Route::post('/reset-password', function (Request $request) {
    $request->validate([
        'token' => 'required',
        'email' => 'required|email',
        'password' => 'required|min:8|confirmed',
    ]);

    $status = Password::reset(
        $request->only('email', 'password', 'password_confirmation', 'token'),
        function (User $user, string $password) {
            $user->forceFill([
                'password' => Hash::make($password),
            ])->setRememberToken(Str::random(60));

            $user->save();

            event(new PasswordReset($user));
        }
    );

    return $status === Password::PasswordReset
        ? redirect()->route('login')->with('status', __($status))
        : back()->withErrors(['email' => [__($status)]]);
})->middleware('guest')->name('password.update');
Password::reset()의 상태 상수:
상태 상수설명
Password::PasswordReset비밀번호 재설정 성공
Password::INVALID_TOKEN토큰이 무효이거나 만료됨
Password::INVALID_USER지정한 이메일의 사용자를 찾을 수 없음

토큰의 유효 기간

config/auth.phpexpire 옵션으로 토큰의 유효 기간을 분 단위로 설정할 수 있습니다. 기본값은 60분입니다.
'passwords' => [
    'users' => [
        'driver' => 'database',
        'expire' => 60, // 60분 후 만료
        'throttle' => 60,
    ],
],
database 드라이버를 사용하는 경우, 만료된 토큰은 데이터베이스에 계속 남습니다. 정기적으로 정리하려면 다음 Artisan 명령을 사용합니다.
php artisan auth:clear-resets
스케줄러로 자동화하는 것을 권장합니다.
use Illuminate\Support\Facades\Schedule;

Schedule::command('auth:clear-resets')->everyFifteenMinutes();

커스터마이징

커스텀 알림 사용

비밀번호 재설정 메일을 커스터마이즈하려면 User 모델에서 sendPasswordResetNotification 메서드를 오버라이드합니다.
use App\Notifications\ResetPasswordNotification;

class User extends Authenticatable
{
    use Notifiable, CanResetPassword;

    /**
     * 비밀번호 재설정 알림 전송
     */
    public function sendPasswordResetNotification($token): void
    {
        $url = 'https://example.com/reset-password?token='.$token;

        $this->notify(new ResetPasswordNotification($url));
    }
}

재설정 링크 URL 커스터마이징

AppServiceProviderboot 메서드에서 ResetPassword::createUrlUsing()을 사용하면 재설정 링크 URL을 변경할 수 있습니다. SPA와 같이 다른 오리진의 프론트엔드로 리다이렉트하고 싶을 때 편리합니다.
use App\Models\User;
use Illuminate\Auth\Notifications\ResetPassword;

public function boot(): void
{
    ResetPassword::createUrlUsing(function (User $user, string $token) {
        return 'https://example.com/reset-password?token='.$token;
    });
}

Trusted Hosts 설정

비밀번호 재설정 링크는 HTTP 요청의 Host 헤더를 사용해 생성됩니다. 부정한 호스트에서 오는 요청을 방지하기 위해 bootstrap/app.php에서 Trusted Hosts 설정을 권장합니다.
->withMiddleware(function (Middleware $middleware) {
    $middleware->trustHosts(at: ['example.com']);
})
비밀번호 재설정 기능을 구현할 경우 Trusted Hosts 설정을 반드시 확인하세요. 설정이 미흡하면 호스트 헤더 인젝션 공격의 위험이 있습니다.

정리

하고 싶은 일방법
재설정 링크 전송Password::sendResetLink(['email' => $email])
비밀번호 재설정Password::reset($credentials, $callback)
테이블 없이 재설정cache 드라이버 설정
만료 토큰 삭제php artisan auth:clear-resets
메일 알림 커스터마이즈sendPasswordResetNotification 오버라이드
재설정 URL 커스터마이즈ResetPassword::createUrlUsing()

다음 단계

인증 입문

Laravel의 인증 시스템 전체 구조를 학습합니다.

알림

메일 알림의 커스터마이징 방법을 자세히 학습합니다.
마지막 수정일 2026년 7월 13일