라라벨에서 api 용 인증모듈은 기본적으로 아래의 명령어로 스케폴딩 할 수 있다.
[code]
php artisan breeze:install api
[/code]
하지만 나의 경우 tymon/jwt-auth 를 이용하여 JWT 인증을 사용하기 때문에 위 스케폴딩을 사용할 수가 없었다.
수많은 시행착오끝에 성공하여 기록해 둔다.
비밀번호를 분실했을시 재설정 링크 URL을 이메일로 보내고 링크를 클릭시 이메일을 변경할 수 있게 한다.
플로우는 아래와 같다.
1) 프론트엔드에서 비밀번호 재설정 클릭
2) 백엔드에서 forgotPassword 메소드를 실행하여 이메일 발송
3) 수신된 이메일에서 버튼 클릭 시 새비밀번호를 입력할 수 있는 프론트엔트 페이지로 연결
4) 새비밀번호 입력 후 백엔드로 전송
5) 백엔드의 resetPasssword 메소드에서 비밀번호 변경 후 성공 통보
1. 라우터 추가
/routes/api.php
[code]
// 재설정 링크가 포함된 이메일 보내기
Route::post(‘/auth/forgot-password’, [ JWTAuthController::class, ‘forgotPassword’ ]);
// 새비밀번호로 변경
Route::post(‘/auth/reset_password’, [ JWTAuthController::class, ‘resetPassword’ ]);
});
[/code]
2. 컨트롤러에 메소드 정의
[code]
<?php
use Illuminate\Auth\Events\PasswordReset; // 패스워드 변경 이벤트
use Illuminate\Support\Facades\Password; // PasswordBroker 의 파사드
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules;
class JWTAuthController extends Controller
{
// …
// 재설정 링크가 포함된 이메일 보내기
public function forgotPassword(Request $request)
{
$request->validate([
’email’ => ‘required|email’
], $request->all());
$status = Password::sendResetLink(
$request->only(’email’)
);
if ($status != Password::RESET_LINK_SENT) {
throw ValidationException::withMessages([
’email’ => [__($status)],
]);
}
return response()->json([‘status’ => __($status)]);
}
// 새비밀번호로 변경
public function resetPassword(Request $request)
{
$request->validate([
‘token’ => [‘required’],
’email’ => [‘required’, ’email’],
‘password’ => [‘required’, ‘confirmed’, Rules\Password::defaults()],
]);
$status = Password::reset(
$request->only(’email’, ‘password’, ‘password_confirmation’, ‘token’),
function ($user) use ($request) {
$user->forceFill([
‘password’ => Hash::make($request->password),
‘remember_token’ => Str::random(60),
])->save();
event(new PasswordReset($user));
}
);
if ($status != Password::PASSWORD_RESET) {
throw ValidationException::withMessages([
’email’ => [__($status)],
]);
}
return response()->json([‘status’ => __($status)]);
}
}
[/code]
3. User 모델에 알림 메소드 추가
위 forgotPassword() 메소드에서 Password::sendResetLink() 로 이메일을 발송시 내부적으로 마지막에 User 모델에서 sendPasswordResetNotification($token) 함수를 호출한다.
그래서 User 모델에 해당 메소드를 구현해 주어야 한다.
app/Models/User.php
[code]
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Auth\Notifications\VerifyEmail;
use Illuminate\Auth\Notifications\ResetPassword;
class User extends Authenticatable implements JWTSubject, MustVerifyEmail
{
use HasApiTokens, HasFactory, Notifiable;
// ….
public function sendPasswordResetNotification($token)
{
$this->notify(new ResetPassword($token)); // 여기서 ResetPassword 알림을 보내면 이메일이 전송된다.
}
}
[/code]
4. 재설정 링크 URL 변경하기
3번까지 하고 forgotPassword() 를 실행하면 링크 URL이 백엔드용 URL로 전송된다.
우리의 경우는 프론트엔드의 비밀번호변경 페이지로 가야하기 때문에 URL을 변경해줘야 한다.
app/Providers/AuthServiceProvider.php
[code]
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Auth\Notifications\ResetPassword;
class AuthServiceProvider extends ServiceProvider
{
// …
public function boot()
{
$this->registerPolicies();
// ResetPassword 알림 클래스의 비밀번호재설정 URL 생성 재정의
ResetPassword::createUrlUsing(function ($notifiable, $token) {
return config(‘app.frontend_url’)
.”/users/password-reset?token={$token}&email={$notifiable->getEmailForPasswordReset()}”;
});
}
}
[/code]
5. 환경설정 파일에 프론트엔드 URL을 추가해준다.
/.env
[code]
FRONTEND_URL=http://example.com
[/code]
/config/app.php
[code]
‘frontend_url’ => env(‘FRONTEND_URL’, ‘http://localhost:3000’),
[/code]
6. 프론트 엔드에 비밀번호 재설정 폼 제작
: 여기서는 nuxt 라 가정하고 아래의 주소에 페이지를 작성한다.
/pages/users/password-reset.vue
새 비밀번호 전송 구문 예
[code language-javascript]
this.$axios
.post(‘http://example.com/api/v1/auth/reset-password’, {
token: this.token,
email: this.email,
password: this.password,
password_confirmation: this.password_confirmation,
})
.then(() => {
this.$alert(‘비밀번호가 변경되었습니다’).then(() => {
this.$router.push(‘/’)
})
})
.catch((err) => {
console.log(err)
})
[/code]