Laravel 8 RESTful Web API 登入認證令牌 JSON Web Token (JWT)
Laravel 8 搭配 tymon/jwt-auth 套件實作完整的 RESTful Web API 登入認證令牌 JSON Web Token (JWT),包含使用者註冊、登入、以 JWT 認證使用 API、更新 JWT 及使用者登出時移除 JWT。
資料庫
使用瀏覽器開啟 phpMyAdmin,在 MariaDB 或 MySQL 新建一個資料庫名稱 laravel_db,編碼與排序使用 utf8mb4_unicode_ci。
Laravel 8 資料庫設定
修改檔案 .env 中第 4 行 DB_DATABASE。(資料庫名稱可自訂,只要與 DB_DATABASE 對應即可)
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_db
DB_USERNAME=xxx
DB_PASSWORD=xxx
自動新增預設資料表:
php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_000000_create_users_table (12.28ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated: 2014_10_12_100000_create_password_resets_table (13.91ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated: 2019_08_19_000000_create_failed_jobs_table (16.50ms)
Migrating: 2019_12_14_000001_create_personal_access_tokens_table
Migrated: 2019_12_14_000001_create_personal_access_tokens_table (28.56ms)
這時查看資料庫會發現多了 5 個資料表 failed_jobs、migrations、password_resets、personal_access_tokens 和 users。
JWT
安裝 JWT (JSON Web 令牌) 套件。
composer require tymon/jwt-auth
# 以上省略 ...
> @php artisan vendor:publish --tag=laravel-assets --ansi
No publishable resources for tag [laravel-assets].
Publishing complete.
將 config/app.php 的 Laravel service providers 和 aliases 各自 include Tymon\JWTAuth 和 JWTAuth、JWTFactory 至結尾:
<?php
// 以上省略
'providers' => [
// 以上省略 ....
Tymon\JWTAuth\Providers\LaravelServiceProvider::class,
],
'aliases' => [
// 以上省略 ....
'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,
'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class,
],
產生 config/jwt.php 設定檔:
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
# 以上省略 ...
Copied File [/vendor/tymon/jwt-auth/config/config.php] To [/config/jwt.php]
Publishing complete.
產生 secret key (JWT 密鑰):
php artisan jwt:secret
jwt-auth secret [whyGL2aPpZlYPiyBTvWDT0hWNq9Lk5OG2V5J979qru8ICfg0DYMsR98ALoKNb1Kk] set successfully.
成功後會將產生的 secret key 存放在檔案 .env 結尾中:
JWT_SECRET=whyGL2aPpZlYPiyBTvWDT0hWNq9Lk5OG2V5J979qru8ICfg0DYMsR98ALoKNb1Kk
設定
修改 app/Models/User.php:
use Tymon\JWTAuth\Contracts\JWTSubject;
。class
宣告要implements JWTSubject
。- 結尾加入兩個 function,
getJWTIdentifier()
和getJWTCustomClaims()
。
<?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;
class User extends Authenticatable implements JWTSubject
{
use HasApiTokens, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
# 可自行加上自訂欄位,例如權限 (DB table 要自行新增)
# 'role',
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
/**
* Get the identifier that will be stored in the subject claim of the JWT.
*
* @return mixed
*/
public function getJWTIdentifier() {
return $this->getKey();
}
/**
* Return a key value array, containing any custom claims to be added to the JWT.
*
* @return array
*/
public function getJWTCustomClaims() {
return [];
}
}
修改 config/auth.php:
- 17 行:原 web 改為 api。
- 44~48 行:新增的程式。
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option controls the default authentication "guard" and password
| reset options for your application. You may change these defaults
| as required, but they're a perfect start for most applications.
|
*/
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| here which uses session storage and the Eloquent user provider.
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| Supported: "session"
|
*/
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'jwt',
'provider' => 'users',
'hash' => false,
],
],
/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| If you have multiple user tables or models you may configure multiple
| sources which represent each model / table. These sources may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
/*
|--------------------------------------------------------------------------
| Resetting Passwords
|--------------------------------------------------------------------------
|
| You may specify multiple password reset configurations if you have more
| than one user table or model in the application and you want to have
| separate password reset settings based on the specific user types.
|
| The expire time is the number of minutes that the reset token should be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
*/
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
'throttle' => 60,
],
],
/*
|--------------------------------------------------------------------------
| Password Confirmation Timeout
|--------------------------------------------------------------------------
|
| Here you may define the amount of seconds before a password confirmation
| times out and the user is prompted to re-enter their password via the
| confirmation screen. By default, the timeout lasts for three hours.
|
*/
'password_timeout' => 10800,
];
建立 Controller
建立認證的 Authentication Controller:
php artisan make:controller AuthController
Controller created successfully.
將下列所有程式碼複製貼上 app/Http/Controllers/AuthController.php:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Models\User;
use Validator;
class AuthController extends Controller
{
/**
* Create a new AuthController instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth:api', ['except' => ['login', 'register']]);
}
/**
* Get a JWT via given credentials. (使用者登入)
*
* @return \Illuminate\Http\JsonResponse
*/
public function login(Request $request)
{
$validator = Validator::make($request->all(), [
# 'role' => 'required|string',
'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);
}
/**
* Register a User. (使用者註冊)
*
* @return \Illuminate\Http\JsonResponse
*/
public function register(Request $request)
{
$validator = Validator::make($request->all(), [
# 'role' => 'required|string',
'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(), 400);
}
$user = User::create(array_merge(
$validator->validated(),
['password' => bcrypt($request->password)]
));
return response()->json([
'message' => 'User successfully registered',
'user' => $user
], 201);
}
/**
* Log the user out (Invalidate the token). (使用者登出,移除 JWT token)
*
* @return \Illuminate\Http\JsonResponse
*/
public function logout()
{
auth()->logout();
return response()->json(['message' => 'User successfully signed out']);
}
/**
* Refresh a token. (更新 JWT token)
*
* @return \Illuminate\Http\JsonResponse
*/
public function refresh()
{
return $this->createNewToken(auth()->refresh());
}
/**
* Get the authenticated User. (以 JWT token 取得使用者資訊)
*
* @return \Illuminate\Http\JsonResponse
*/
public function userProfile()
{
return response()->json(auth()->user());
}
/**
* Get the token array structure.
*
* @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()
]);
}
}
路由
設定 routes/api.php,第 6 行、24 ~ 33 行為新增的程式碼:
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\AuthController;
/*
|--------------------------------------------------------------------------
| 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!
|
*/
// Laravel 預設
//Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
// return $request->user();
//});
Route::group([
'middleware' => 'api',
'prefix' => 'auth'
], function ($router) {
Route::post('/register', [AuthController::class, 'register']); // 使用者註冊
Route::post('/login', [AuthController::class, 'login']); // 使用者登入 (回傳 JWT token 及使用者資訊)
Route::get('/user-profile', [AuthController::class, 'userProfile']); // 以 JWT token 取得使用者資訊
Route::post('/refresh', [AuthController::class, 'refresh']); // 更新 JWT token
Route::post('/logout', [AuthController::class, 'logout']); // 使用者登出,移除 JWT token
});
測試
先到 Download Postman | Get Started for Free 下載並安裝可以發送和接收 HTTP 請求的軟體。
使用者註冊
使用者註冊網址 https://laravel8-jwt.footmark.com.tw/api/auth/register,使用 POST 在 Body 傳送給伺服器的資訊。
{
"name": "footmark",
"email": "footmark.info@gmail.com",
"password": "abc123",
"password_confirmation": "abc123"
}
Postman POST 使用者註冊,回傳註冊成功資訊:
使用者登入
使用者註冊登入網址 https://laravel8-jwt.footmark.com.tw/api/auth/login,使用 POST 在 Body 傳送給伺服器的資訊。
{
"email": "footmark.info@gmail.com",
"password": "abc123"
}
Postman POST 使用者登入,回傳 JWT tokin 如下紅框,後續要請求的 API 都得發送這個 tokin,來讓 Laravel 確認您有權限能使用 API。
以 JWT token 使用 API 取得使用者資訊
以 JWT token 使用 API 取得使用者資訊網址 https://laravel8-jwt.footmark.com.tw/api/auth/user-profile,使用 GET 而 Auth 認證類型須使用 Bearer Token,並將上述取得的 Token 複製並貼至右則的 Token 欄位。
如果 JWT Token 認證沒有問題,則 API 就會回傳使用者資訊。
使用者更新 JWT token
更新 JWT token 網址 https://laravel8-jwt.footmark.com.tw/api/auth/refresh,使用 POST 而 Auth 認證類型須使用 Bearer Token,並將上述取得的 Token 複製並貼至右則綠框的 Token 欄位。
紅框為 API 回傳的新 Token,後續 API 認證就要使用這個新 Token 了。
使用者登出,移除 JWT token
使用者登出,移除 JWT token 網址 https://laravel8-jwt.footmark.com.tw/api/auth/logout,使用 POST 而 Auth 認證類型須使用 Bearer Token,並將上述取得的 Token 複製並貼至右則的 Token 欄位。
紅框顯示使用者已登出,因為 Tokin 已刪除無法使用了。
參考
本著作係採用創用 CC 姓名標示-相同方式分享 3.0 台灣 授權條款授權.