چگونه از jwt در لاراول استفاده کنیم ؟

سلام دوستان این اولین پستی هست که در مورد فنی می نویسم و خیلی موضوعات مختلف هست که دوست دارم بهش بپردازم و کمک کنم تا بقیه راحت تر مشکلشون حل بشه. امیدوارم وقتم آزاد تر بشه و بتونم مسائلی رو که در ۶ سال پیش باهاشون دستو پنجه نرم کردم را باهاتون در میون بگذارم.

خیلی از ماها احتمالا بخوایم برای یک اپ که از طریق مثلا یک rest api با سرور در ارتباط هست از یک توکن استفاده کنیم . یکی از راه ها استفاده از توکن هست که بعد از لاگین یک توکن به کاربر داده می شود و کاربر در درخواست های بعدی این توکن را به سمت سرور ارسال میکنه تا احراز هویت بشه. اما با این کار اتفاقی که می افته اینکه سرور با توکن گرفته شده یک درخواست به پایگاه داده می دهد که کاربر با توکن ارسال شده پیدا بشه بعد از اون با شناسه اصلی کاربر مثلا کامنت های اخیر کاربر گرفته بشه.

اما jwt یا همان json web token چون دارای یک امضای دیجیتال هست و بین client و server بصورت امن جابه جا میشه یعنی شما نیازی به چک کردن توکن در دیتابیس ندارید و حتی نگه داریش هم نمی کنید!!!‌ یعنی یک درخواست کمتر و در اپلیکیشن های بزرگتر کلی درخواست کمتر .... البته این یکی از مزیت های استفاده از jwt هست و مزیت های دیگه هم داره شما ممکنه هر جایی براتون مناسب نباشه که از jwt استفاده کنید. چون در هر مسیری در هر پروژه ای نیاز شماست که نشون میده چه ابزاری برای شما مناسبه اگه میخواید نحوه کار کردن json web token رو بدونید می تونید از این لینک استفاده کنید.

اما برای استفاده از jwt در لاراول میتونید از پکیج tymondesigns/jwt-auth استفاده کنید.

برای اینکار در پروژه لاراوالی خودتون با composer این پکیج رو نصب کنید :

composer require tymon/jwt-auth

پس از نصب این پکیج خط زیر رو به providers در config/app.php اضافه کنید :

Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class

و دو خط زیر رو هم به قسمت aliases در همان فایل اضافه کنید:

JWTAuth => Tymon\JWTAuth\Facades\JWTAuth::class,
JWTFactory => Tymon\JWTAuth\Facades\JWTFactory::class

پس از اون دستور زیر روی اجرا کنید تا فایل jwt.php در قسمت config ایجاد بشه و بتونید تنظیمات رو تغییر بدید :

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\JWTAuthServiceProvider"

پس از اون شما به یک secret-key نیاز دارید که با دستور زیر میتونید اون رو بسازید:

php artisan jwt:generate

تا به اینجا شما پکیج رو نصب کردید اما الان نیاز به معرفی middleware ها دارید تا بتونید روی route های دلخواهتون حتما احراز هویت بخواید ( مثلا ویرایش و ساخت یک محصول جدید یا ... که به لاگین بودن کاربر نیاز داره ) برای اینکار در فایل app/http/Kernel.php در قسمت routeMiddleware این دو middleware رو اضافه کنید

'jwt.auth' => 'Tymon\JWTAuth\Middleware\GetUserFromToken',
'jwt.refresh' => 'Tymon\JWTAuth\Middleware\RefreshToken'

به کمک jwt.auth ما میتونیم روی گروهی از route ها درخواست دهنده رو ملزم به لاگین کنیم و به کمک jwt.refresh ما می تونیم برای رفرش کردن توکن اقدام کنیم.

حالا می ریم که route ها خودمون رو از این middleware ها عبور بدیم در فایل routes/api.php اینگونه می نویسیم :

Route::post('login', 'AuthController@login');
Route::post('register', 'AuthController@register');

// Refresh route
Route::get('/refresh',function(){
})->middleware('jwt.refresh');

// Login required routes
Route::group(['middleware' => ['jwt.auth']], function() {
    Route::Resource('/product','ProductControler');
});

صرف آشنایی شما با لاراول نیاز به توضیح بیشتر در این قسمت نیست .

حال باید بدنه AuthController رو بسازیم ( شما باید یک controller بسازید اسم اون دلخواهه ... فقط باید در route هاتون هم به همون controller اشاره کنید )

/**
 * 
 *
 * @param Request $request
 * @return \Illuminate\Http\JsonResponse
 */
public function login(Request $request)
{
    $rules = [
        'email' => 'required|email',
        'password' => 'required',
    ];
    $input = $request->only('email', 'password');
    $validator = Validator::make($input, $rules);
    if ($validator->fails()) {
        $error = $validator->messages()->toJson();
        return response()->json(['success' => false, 'error' => $error]);
    }
    $credentials = [
        'email' => $request->email,
        'password' => $request->password
    ];
    try {
        // attempt to verify the credentials and create a token for the user
        if (!$token = JWTAuth::attempt($credentials)) {
            return response()->json(['success' => false, 'error' => 'Wrong email or password.'], 401);
        }
    } catch (JWTException $e) {
        return response()->json(['success' => false, 'error' => 'could_not_create_token'], 500);
    }
    return response()->json(['success' => true, 'data' => ['token' => $token]]);
}

public function register(Request $request)
{
     // you registration section .... 
}
/**
 * Log out
 * Invalidate the token, so user cannot use it anymore
 * They have to relogin to get a new token
 *
 * @param Request $request
 */
public function logout(Request $request)
{
    $this->validate($request, ['token' => 'required']);
    try {
        JWTAuth::invalidate($request->input('token'));
        return response()->json(['success' => true]);
    } catch (JWTException $e) {
        return response()->json(['success' => false, 'error' => 'Destory faild'], 500);
    }
}

در قسمت لاگین ما اعتبار سنجی می کنیم که آیا کاربر با یوزنیم و پسورد وارد شده قادر به لاگین هست یا نه و بعد توکن ساخته شده رو براش میفرستیم.

نکته:

زمان های expire توکن و رفرش توکن رو میتونید در فایل config/jwt.php تغییر بدید.

اما یک نکته همون طوری که گفتم ما توکنی رو سمت سرور ذخیره نمیکنیم پس اگر کاربری بخواد لاگ ات کنه یا در حقیقت توکن فعلیش رو از اعتبار ساقط کنه چه باید بکنه ما که در سمت سرور جایی نداریم که بخوایم اون توکن رو پاک کنیم. در jwt اگر شما با همان زمان token expire کارتون راه نمیفته ( که خیلی ها میفته ) و حتما میخواید لاگ ات کنید ( علاوه بر اینکه میتونید سمت کاربر توکن رو هم حذف کنید ) در حقیقت توکن ها رو میبرن توی یک black list و اگر کاربر درخواست داد میبینن که آیا توکن توی بلک لیست هست اگر نیست اجازه دسترسی بهش نمیدن اما یه سوال اگر اینکار رو بکنیم که در حقیقت دوباره داریم یه درخواست به پایگاه داده اضافه می کنیم و این یک مشکله دقیقا درسته به خاطر همین:

یکی از راهکارها اینه که مثلا توکن های بلک لیست رو توی لایه های کش مثل redis نگه داری می کنن و بعد از اینکه زمان expire توکن رسید اونو پاک میکنن. که با توجه به زمان توکن این لیست مثلا هر یک ساعت یکبار کلی ریزش میکنه و اونقدر بزرگ نمیشه ( بسته به بزرگی پروژه و تعداد کاربرهای متفاوت خواهد بود ).

شما میتونید پس از این راهکارها یک یوزر در لاروال بسازید و بعد با postman درخواستتون رو با header :

Authorization Bearer [توکن شما] 

به سمت سرورتون ارسال کنید و نتیجه رو ببینید.

امیدوارم آموزش بالا تونسته باشه بهتون کمک کنه.