Và JWT token cũng thế, ở khuôn khổ bài này mình không nói về jwt là gì. Mình chỉ hướng dẫn cho các bạn cách nhúng jwt vào laravel cho api.
Cài đặt
1. cài package
Chạy lệnh thông qua composer :
composer require tymon/jwt-auth
2. config provider và alias
Sau khi install package hoàn tất, bạn cần thêm service provider vào mảng provider trong file config app.php :
Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class,
cũng tại file app.config đó bạn thêm aliases cho 2 lớp thường dùng:
'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,
'JWTFactory' => 'Tymon\JWTAuth\Facades\JWTFactory'::class,
3. tạo file config để jwt hoạt động
Sau cùng bạn cần publish config của jwt ( kiểu như nó tạo cái file config vào hệ thống laravel )
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\JWTAuthServiceProvider"
4. Tạo secret key
Theo lý thuyết jwt dĩ nhiên muốn tạo ra 1 token bạn cần cung cấp 1 secret key
tạo secret key trong laravel khá dễ :
Nếu bạn sử dụng jwt để thực hiện xác thực cho việc trao đổi thông tin giữa 2 server
thì 2 key của 2 server phải giống nhau thì mới có thể giải mã được ...
Vì vậy, bạn có thể tạo 1 key bất kỳ bên server này và copy cho server kia.
bài hướng dẫn giải mã jwt của php laravel trong nodejs
5. sửa lại model user để tiêm cái id user vào claim
bạn cần implements cái interface tên JWTSubject và thực thi 2 phương thức getJWTIdentifier, getJWTCustomClaims mục tiêu là để khi tạo token thì jwt của bạn được gắn chặt với 1 vái value là id của user. Sau đó khi giải mã thì ta được 1 object user tương ứng
namespace App\Models;
/// ...
use Tymon\JWTAuth\Contracts\JWTSubject;
class User extends Authenticatable implements JWTSubject
{
use HasFactory, Notifiable;
/**
* @return int
*/
public function getJWTIdentifier() {
return $this->getKey();
}
/**
* @return array
*/
public function getJWTCustomClaims() {
return [
"iss" => "http://localhost",
"ahihi" => "đồ ngốc CustomClaims"
];
}
//// .....
}
6. lưu ý thông tin file config
Secret Key - secret
Cái tên nói lên tất cả 😄 Mục 4 mình vừa trình bày là cái key này, khi bạn muốn 2 server giải mã qua lại thì 2 server phải có key giống nhau.
Mặc định load trong env cái biến JWT_SECRET không thì nó lấy đại
'secret' => env('JWT_SECRET', 'hung-dep-trai'),
JWT time to live - ttl
1 token tạo ra phải có thười gian sống (tính bằng phút). Khuyến nghị ngắn thôi.
Refresh time to live - refresh_ttl
Đây là thời gian (tính bằng phút) bạn có thể tạo mới một token khác từ token cũ mà ko cần xác thực lại...
'refresh_ttl' => 20160, //2 week
Tạo một token đơn giản
Tạo một token dự trên user
class LoginCotnroller trong thư mục api như sau:
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Facades\JWTAuth;
class LoginController extends Controller
{
/**
* get all login
* php artisan make:resource ApiAuthResource
*/
public function login(Request $request){
// lấy thông tin từ các request gửi lên
$credentials = $request->only('email', 'password');
try {
// xác nhận thông tin người dùng gửi lên có hợp lệ hay không
if (! $token = JWTAuth::attempt($credentials)) {
return response()
->error(
'có lỗi validate trong controller',
['error' => 'invalid_credentials'],
Response::HTTP_UNAUTHORIZED
)
->setStatusCode(Response::HTTP_UNAUTHORIZED);
}
} catch (JWTException $e) {
// Xử lý ngoại lệ
return response()
->error(
'có lỗi validate trong controller',
['error' => 'could_not_create_token'],
Response::HTTP_INTERNAL_SERVER_ERROR
)
->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR);
}
return response()
->success('Your custom login', $token)
->setStatusCode(Response::HTTP_OK);
}
/**
* Nếu bạn sử dụng function login ở trên nhìn thấy phức tạp hay thấy gớm, khó hiểu quá thì dùng function dưới này nhìn dễ hiểu hơn 1 tí :D
*/
public function simpleLogin(Request $request){
// lấy thông tin từ các request gửi lên
$credentials = $request->only('email', 'password');
try {
// xác nhận thông tin người dùng gửi lên có hợp lệ hay không
if (! $token = JWTAuth::attempt($credentials)) {
return response()->json(['error' => 'invalid_credentials'], 401);
}
} catch (JWTException $e) {
// Xử lý ngoại lệ
return response()->json(['error' => 'could_not_create_token'], 500);
}
// xác nhận thành công thì trả về 1 token hợp lệ
return response()->json(compact('token'));
}
}
Tạo token dự trên một đối tượng .
Ngoài phương pháp thông thường,
chúng ta còn có thể tạo một mã Token dựa trên một user instance như sau :
$user = User::where('email', 'hung_dep_trai@gmail.com')->first();
if(!$user){
return response()->json(['error' => 'user_not_found'], 500);
}
$token = JWTAuth::fromUser($user);
claims - ghi thêm dữ liệu vào token
$customClaims = ['foo' => 'bar', 'baz' => 'bob'];
// JWTAuth::attempt($credentials, $customClaims); -> phương thức để login
// or
JWTAuth::fromUser($user, $customClaims);
Và đương nhiên, những dữ liệu bạn ghi thêm vào sẽ có mặt cùng với những dữ liệu khác khi giải mã token.
Lưu ý: Hết sức chú ý những dữ liệu bạn ghi thêm vào token vì chúng sẽ làm tăng độ dài cũng như dung lượng của token
Tạo token từ 1 object bất kì
$customClaims = ['foo' => 'bar', 'baz' => 'bob'];
$payload = JWTFactory::make($customClaims);
$token = JWTAuth::encode($payload);
Xác thực
Có token rồi thì khi người dùng gọi api phải gửi lên kèm header hoặc query params
Với token này thì server có thể check token đó đúng không ? nếu đúng thì lấy được thông tin đăng nhập .
Đoạn mã đó có dạng như sau :
Authorization: Bearer {your_token_here}
ví dụ cụ thể:
GET /api/v1/users HTTP/1.1
Host: localhost:80
User-Agent: rest-client
Accept-Language: en-GB,en-US;q=0.8,en;q=0.6,zh-CN;q=0.4
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3RcL2FwaVwvdjFcL2xvZ2luIiwiaWF0IjoxNjI1NTMwNDY2LCJleHAiOjE2MjU1MzQwNjYsIm5iZiI6MTYyNTUzMDQ2NiwianRpIjoibFhCZUlmMFFOMk9rTGlOZSIsInN1YiI6MiwicHJ2IjoiMjNiZDVjODk0OWY2MDBhZGIzOWU3MDFjNDAwODcyZGI3YTU5NzZmNyJ9.nAGhQWgLSPd-yX_xMRvNn82NZpXjMBKY61G0FHiIxV0
content-type: application/json
################################
Hoặc nếu bạn không muốn sử dụng header thì có thể sử dụng query string . Ví dụ
http://domain.xyz/users?token={your_token_here}
Để lấy token từ đoạn mã gửi lên thì bạn có thể làm như sau:
JWTAuth::parseToken();
$user = JWTAuth::parseToken()->authenticate();
Để lấy giá trị token thì có thể làm như sau :
$token = JWTAuth::getToken();
public function getAuthenticatedUser()
{
try {
if (! $user = JWTAuth::parseToken()->authenticate()) {
return response()->json(['user_not_found'], 404);
}
} catch (Tymon\JWTAuth\Exceptions\TokenExpiredException $e) {
return response()->json(['token_expired'], $e->getStatusCode());
} catch (Tymon\JWTAuth\Exceptions\TokenInvalidException $e) {
return response()->json(['token_invalid'], $e->getStatusCode());
} catch (Tymon\JWTAuth\Exceptions\JWTException $e) {
return response()->json(['token_absent'], $e->getStatusCode());
}
return response()->json(compact('user'));
}
exception global
Mình sẽ viết 1 bài nói về cách dùng exception trong laravel trông có vẻ xịn hơn khi viết api hay xử lý các vấn đề về error để code clean hơn. Riêng phần này mình nói ngắn gọn để bắt lỗi exception global là bạn vào file app/Exceptions/Handler.php
: bnaj thêm khúc này vào để xuất json:
public function render($request, Exception $e)
{
if ($e instanceof Tymon\JWTAuth\Exceptions\TokenExpiredException) {
return response()->json(['token_expired'], $e->getStatusCode());
} else if ($e instanceof Tymon\JWTAuth\Exceptions\TokenInvalidException) {
return response()->json(['token_invalid'], $e->getStatusCode());
}
return parent::render($request, $e);
}