Hello ae, Hôm nay mình sẽ cùng build một mô hình Auth, register/login/logout bằng Laravel + Reactjs sử dụng JSON Web Token hay còn gọi là JWT
JWT là một token mà mình trong bài này sẽ generate ra để xác thực một user đã login hay chưa. Và thường được sử dụng cho những trang web có sử dụng Single Sign On,.. Luồng xử lý sẽ như thế này!
Client gửi request đến sever kèm theo username + password
Sever sẽ Auth và generate JWT và trả về cho client
Client lưu lại JWT và gửi các request sau đó kèm theo JWT với Authorization header hoặc truyền trực tiếp trên thanh address bar
Sever check JWT và trả response về cho client
Về cấu trúc của JWT JWT gồm các thành phần như ở dưới đây: Cấu trúc của nó gồm 3 phần chính Header, Payload và Signature và ở trong này này mình sẽ quan tâm nhất đến phần Payload sẽ được thể hiện ở phần code phía sever và bài này mình chọn laravel. Ae có thể sử dụng cái này để generate một cái JWT mà mình muốn https://jwt.io/
1. Building BackEnd (Cài đặt laravel, database, jwt-auth)
Laravel 5.7, nếu sử dụng package jwt-auth ở trên ae không nên cài bản laravel mới nhất vì thời điểm mình làm demo này thì package này chưa hỗ trợ tốt bản laravel mới nhất, có thể gây ra lỗi rất khó chịu:
Ta sẽ tạo một UserControllerđể handle các request register và login
php artisan make:controller UserController
với nội dung dưới đây:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\User;
use JWTAuth;
use JWTAuthException;
use Validator;
class UserController extends Controller
{
private function getToken($email, $password)
{
$token = null;
try {
if (!$token = JWTAuth::attempt( ['email'=>$email, 'password'=>$password])) {
return response()->json([
'response' => 'error',
'message' => 'Password or email is invalid',
'token'=>$token
]);
}
} catch (JWTAuthException $e) {
return response()->json([
'response' => 'error',
'message' => 'Cannot create token',
]);
}
return $token;
}
public function register(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required',
'email' => 'required|unique:users',
'password' => 'required',
]);
if ($validator->fails()) {
return response()->json($validator->errors());
}
$payload = [
'password'=>\Hash::make($request->password),
'email'=>$request->email,
'name'=>$request->name,
'auth_token'=> ''
];
$user = new User($payload);
if ($user->save())
{
$token = self::getToken($request->email, $request->password);
if (!is_string($token)) return response()->json(['success'=>false,'data'=>'Token generation failed'], 201);
$user = User::where('email', $request->email)->get()->first();
$user->auth_token = $token;
$user->save();
$response = ['success'=>true,'auth_token'=>$token];
}
else
$response = ['success'=>false, 'data'=>'Register Failed'];
return response()->json($response, 201);
}
}
public function login(Request $request)
{
$user = User::where('email', $request->email)->get()->first();
if ($user && \Hash::check($request->password, $user->password))
{
$token = self::getToken($request->email, $request->password);
$user->auth_token = $token;
$user->save();
$response = ['success'=> true, 'auth_token'=>$user->auth_token];
}
else
$response = ['success' => false,'data' => 'User doesnt exist'];
return response()->json($response, 201);
}
Mỗi một lần login khác nhau chúng ta sẽ có một JWT khác nhau. Nhăc lại về luồng chạy:
Browser Login (OK) => Sever generate token => lưu token vào database => trả về cho client. Client gửi request kèm theo header với jwttoken =>Sever check và gửi về responses
Tiếp theo ta sẽ update routes trong file /routes/api.php
Ở đây ta có window.Laravel từ javascript tiếp nhận các giá trị được truyền từ laravel vào và ta sẽ tiến hành append tất cả nội dung vào thẻ div có id là app
Tiếp theo ta sẽ sử dụng và import React Router v4 bằng command sau:
npm install --save react-router-dom@4.2.2
Và ta sẽ sử dụng history để quản lý lịch sử của browser
npm install --save history
Step #2: Chúng tiếp tục tiến hành tạo các components của ReactJs như dưới đây
Trong thư mục /resources/js/components tạo các components sau đây và mình sẽ fill nội dung sau.
Tiếp tục tạo 1 file /resources/js/Http.js để thêm 1 vài options khi sử dụng axios để gửi request lên sever. Bắt đầu đi vào phần nội dung của các components:
Step #3: Fill nội dung component app. Đầu tiên với file Http.js
Giống như một instance của axios. Chúng ta sẽ tạo thêm option cho Http khi chúng ta access vào các route chạy qua middleware Auth Jwt bằng cách thêm Authentication vào header trông như thế này. Http.defaults.headers.common['Authorization'] = 'Bearer ' + auth_token;
File /resources/js/components/app.js
require('./bootstrap');
import React from 'react'
import { render } from 'react-dom'
import {
Router,
Route,
Switch
} from 'react-router-dom'
import { createBrowserHistory } from 'history'
import Login from './components/auth/Login'
import Register from './components/auth/Register'
import Page from './components/users/Page'
const history = createBrowserHistory();
render (
<Router history={history}>
<Switch>
<App>
<Route path='/' exact component={Page} />
<Route path='/login' exact component={Login} />
<Route path='/register' exact component={Register} />
</App>
</Switch>
</Router>, document.getElementById('app'))
File app này chúng ta đã tiến hành import các components, định nghĩa route cho để điều hướng ứng dụng.
Step #4: Trở về định nghĩa route cho laravel: Trong file routes/web.php chúng ta sẽ thêm dòng dưới đây để bắt tất cả các request về file welcome.blade.php => bắt tất cả các request sang sử dụng react router.
Như ở đây ae có thể thấy /api/user sẽ chạy qua middleware của jwt-auth mà chúng ta định nghĩa. Và khi cần chạy vào route này chúng ta sẽ cần thêm option Authentication vào header của request.
Ta thấy ở trong methodgetToken() có gọi đến JWTAuth::attempt( ['email'=>$email, 'password'=>$password]) Ở đây mình sử dụng package jwt-auth và khi gọi đến hàm này ta có truyền 2 params là 'email'=>$email và password'=>$password] , theo đúng như tên gọi của nó JSON Web token, mình luôn phải truyền params theo kiểu json, cụ thể là mình đang truyền ['email'=>$email, 'password'=>$password] vào phần Payload trong JWT. Nhắc lại cấu trúc của JWT 1 chút ở đây: Cũng vì mình sử dụng package nên mình không cần quan tâm đến phần header hay Signature nữa, chỉ cần truyền payload vào => done!