config/app.php
[code]
‘providers’ => [
….
Tymon\JWTAuth\Providers\LaravelServiceProvider::class,
….
],
‘alias’ => [
….
‘JWTAuth’ => Tymon\JWTAuth\Facades\JWTAuth::class,
‘JWTFactory’ => Tymon\JWTAuth\Facades\JWTFactory::class,
]
[/code]
routes/api.php
[code]
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\JWTAuthController;
use Illuminate\Foundation\Auth\EmailVerificationRequest;
use App\Models\User;
use Illuminate\Auth\Events\Verified;
/*
|————————————————————————–
| API Routes
|————————————————————————–
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the “api” middleware group. Enjoy building your API!
|
*/
Route::group([
‘middleware’ => [‘api’, ‘throttle:6,1’],
], function ($router) {
// 인증되지 않은 사용자 페이지
$router->get(‘unauthorized’, function () {
return response()->json([
‘status’ => ‘error’,
‘message’ => ‘Unauthorized’
], 401)->name(‘api.unauthorized’);
});
# 회원 and 인증
$router->group([
‘prefix’ => ‘auth’
], function ($router) {
# 비회원용
Route::post(‘/login’, [JWTAuthController::class, ‘login’]);
Route::post(‘/register’, [JWTAuthController::class, ‘register’]);
Route::get(‘/verify/{id}/{hash}’, [JWTAuthController::class, ‘verifyEmail’])
->middleware(‘signed’)
->name(‘verification.verify’);
# 회원전용
$router->group([
‘middleware’ => ‘auth:api’
], function () {
Route::post(‘/logout’, [JWTAuthController::class, ‘logout’]);
// Route::post(‘/refresh’, [JWTAuthController::class, ‘refresh’]); // 미들웨어에서 자동으로 처리한다
Route::get(‘/user-profile’, [JWTAuthController::class, ‘userProfile’])
->middleware(‘verified’);
Route::patch(‘/{id}’, [JWTAuthController::class, ‘edit’])->middleware(‘verified’);
Route::post(‘/resend_verify’, [JWTAuthController::class, ‘resendVerifyEmail’])
->middleware(‘auth:api’)
->name(‘verification.send’);
});
});
});
[/code]
app/Models/User.php
[code]
<?php
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;
// jwt 를 사용하기 위해서 JWTSubject 인터페이스를 구현하고
// 이메일을 보내기 위해서 MustVerfiyEmail 인터페이스를 구현해야 한다.
class User extends Authenticatable implements JWTSubject, MustVerifyEmail
{
use HasApiTokens, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
‘name’,
’email’,
‘password’,
];
/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
‘password’,
‘remember_token’,
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
’email_verified_at’ => ‘datetime’,
];
public function getJWTIdentifier()
{
return $this->getKey();
}
public function getJWTCustomClaims()
{
return [
‘name’ => $this->name,
’email’ => $this->email,
];
}
// public function hasVerifiedEmail()
// {
// }
// /**
// * Mark the given user’s email as verified.
// *
// * @return bool
// */
// public function markEmailAsVerified()
// {
// }
/**
* Send the email verification notification.
*
* @return void
*/
public function sendEmailVerificationNotification()
{
$this->notify(new VerifyEmail);
}
/**
* Get the email address that should be used for verification.
*
* @return string
*/
// public function getEmailForVerification()
// {
// }
}
[/code]
app/Http/Controllers/JWTAuthController.php
[code]
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Events\Verified;
use Validator;
use App\Models\User;
class JWTAuthController extends Controller
{
/**
* 로그인
*
* @return \Illuminate\Http\JsonResponse
*/
public function login(Request $request)
{
$validator = Validator::make($request->all(), [
’email’ => ‘required|email’,
‘password’ => ‘required|string|min:6’,
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
if (! $token = auth()->attempt($validator->validated())) {
return response()->json([‘error’ => ‘Unauthorized’], 401);
}
return $this->createNewToken($token);
}
/**
* 회원가입
*
* @return \Illuminate\Http\JsonResponse
*/
public function register(Request $request)
{
$validator = Validator::make($request->all(), [
‘name’ => ‘required|string|between:2,100’,
’email’ => ‘required|string|email|max:100|unique:users’,
‘password’ => ‘required|string|confirmed|min:6’,
]);
if ($validator->fails()) {
return response()->json($validator->errors()->toJson(), 422);
}
$user = User::create(array_merge(
$validator->validated(),
[‘password’ => bcrypt($request->password)]
));
event(new Registered($user));
return response()->json([
‘message’ => ‘User successfully registered’,
‘user’ => $user
], 201);
}
/**
* 로그아웃
*
* @return \Illuminate\Http\JsonResponse
*/
public function logout()
{
auth()->logout();
return response()->json([‘message’ => ‘User successfully signed out’]);
}
/**
* 토큰리플래시: 미들웨어에서 자동으로 처리하므로 필요없다
*
* @return \Illuminate\Http\JsonResponse
*/
// public function refresh()
// {
// return $this->createNewToken(auth()->refresh());
// }
/**
* 로그인한 사용자 정보 보기
*
* @return \Illuminate\Http\JsonResponse
*/
public function userProfile()
{
return response()->json(auth()->user());
}
/**
* 토큰생성
*
* @param string $token
*
* @return \Illuminate\Http\JsonResponse
*/
protected function createNewToken($token)
{
return response()->json([
‘access_token’ => $token,
‘token_type’ => ‘bearer’,
‘expires_in’ => auth()->factory()->getTTL() * 60,
‘user’ => auth()->user()
]);
}
/**
* 사용자정보 수정
*
* @param string $token
*
* @return \Illuminate\Http\JsonResponse
*/
public function edit($id, Request $request)
{
$request->validate([
‘name’ => ‘required|string|between:2,100’,
‘password’ => ‘string|confirmed|min:6’,
], $request->all());
$user = auth()->user();
$user->name = $request->name;
if ($request->password) {
$user->password = bcrypt($request->password);
}
$user->update();
return response()->json([
‘message’ => ‘User successfully updated’,
‘data’ => $user
]);
}
/**
* 이메일 인증
*
* @param string $token
*
* @return \Illuminate\Http\JsonResponse
*/
// 공식문서에는 EmailVerificationRequest 를 사용하라고 하지만 api 에서는 저장된 토큰이 없기 때문에 그냥 Request 를 사용해야 한다.
public function verifyEmail($id, $hash, Request $request)
{
$user = User::find($id);
if ($user->hasVerifiedEmail()) {
return redirect(env(‘FRONT_URL’) . ‘/email/verify/already-success’);
}
if ($user->markEmailAsVerified()) {
event(new Verified($user));
}
return redirect(env(‘FRONT_URL’).’/auth/verified’);
}
/**
* 이메일 인증 다시 보내기
*
* @param string $token
*
* @return \Illuminate\Http\JsonResponse
*/
public function resendVerifyEmail(Request $request)
{
$request->user()->sendEmailVerificationNotification();
return response()->json([
‘message’ => ‘Verification link sent’,
]);
}
}
[/code]
app/Middleware/Authenticate.php
[code]
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Closure;
use Auth;
use JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param \Illuminate\Http\Request $request
* @return string|null
*/
protected function redirectTo($request)
{
if ($request->expectsJson() || $request->is(‘api/*’)) {
return route(‘api.unauthorized’);
}
return route(‘login’);
}
/**
* api 일때 토큰이 만료되었으면 refresh_ttl 이 안지났으면 자동으로 새로 갱신해준다.
*
* @return mixed
*/
public function handle($request, Closure $next, …$guards)
{
if ($request->is(‘api/*’)) {
try {
JWTAuth::parseToken()->authenticate();
} catch (\Exception $e) {
if ($e instanceof TokenInvalidException) {
$status = 401;
$message = ‘This token is invalid. Try login’;
return response()->json(compact(‘status’, ‘message’), 401);
} elseif ($e instanceof TokenExpiredException) {
try {
$refreshed_token = Auth::guard(‘api’)->refresh();
JWTAuth::setToken($refreshed_token)->toUser();
$response = $next($request);
$response->headers->set(‘Authorization’, ‘Bearer ‘.$refreshed_token);
return $response;
} catch (JWTException $e) {
return response()->json([
‘code’ => 103,
‘message’ => ‘Token cannot be refreshed, Try login again’
]);
}
} else {
$message = ‘Authorization Token not found’;
return response()->json(compact(‘message’), 404);
}
}
return $next($request);
} else {
$this->authenticate($request, $guards);
return $next($request);
}
}
}
[/code]
발송되는 이메일을 커스터마이징 하기 위해 publish 한다.
[code]
php artisan vendor:publish –tag=laravel-notifications
[/code]
resourses/vendor/notifications/email.blade.php 파일이 생성된다
.env 에 아래 추가
[code]
MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
# 구글 계정
[email protected]
# 구글앱비밀번호: 구글 계정 -> 보안 -> 2차인증체크 -> 앱비밀번호 생성
MAIL_PASSWORD=xxxxxxxxxxx
MAIL_ENCRYPTION=tls
# 보내는 사람 이메일
[email protected]
MAIL_FROM_NAME=”${APP_NAME}”
# 토큰 만료시간: 30분
JWT_TTL=30
# 리플래시토큰 만료시간 : 10시간
JWT_REFRESH_TTL=600
# jwt 시크릿 키
JWT_SECRET=wUIQXkre895uTFdmmrre0OCWzNWbMLdxYsmyZaQ6lEgySN4YifLd4TVFnfdnlv87
# 프론트엔드 URL
FRONT_URL=http://test.kr
[/code]