[Laravel] jwt 인증과 api 에서 이메일 인증 소스 > IT 기술백서

IT 기술백서

직접 알아내거나 검색하기 귀찮아서 모아 둔 것

php | [Laravel] jwt 인증과 api 에서 이메일 인증 소스

본문

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]

 

댓글 0개

등록된 댓글이 없습니다.

Menu