資料庫設定
使用 make:migration
建立新的 migration:
1
|
php artisan make:migration --table=users add_api_token
|
加入 api_token
欄位:
1
2
3
4
5
6
7
8
9
|
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('api_token', 80)->after('password')
->unique()
->nullable()
->default(null);
});
}
|
執行遷移:
產生 token
官方的範例是在使用者註冊時同時產生 token,但如果沒有另外實作更新機制的話可能不是很安全,這邊選擇的實作方式是在使用者登入時重新產生一組 token,這樣使用者不用自己管理 token,也不會永遠使用同一組 token。
使用 make:listener
建立一個 listener:
1
|
php artisan make:listener SuccessfulLogin
|
修改 app/Listeners/SuccessfulLogin.php
,加入以下內容,讓事件觸發時更新 API token。
1
2
3
4
5
6
|
public function handle()
{
$user = Auth::user();
$user->api_token = Str::random(80);
$user->save();
}
|
最後修改 app/Providers/EventServiceProvider.php
,將我們剛剛新增的 listener 綁定到使用者登入的事件:
1
2
3
4
5
6
7
|
protected $listen = [
// ...
'Illuminate\Auth\Events\Login' => [
'App\Listeners\SuccessfulLogin',
],
// ...
];
|
Illuminate\Auth\Events\Login
是 Laravel 內建的事件類型。
這時候進行登入,可以發現 api_token
在登入後會自動更新。
更新路由
只要對指定的路由增加 auth:api
的 middleware,即可讓 Laravel 收到 HTTP request 時自動驗證使用者端的 token。
routes/api.php
1
2
3
|
Route::middleware('auth:api')->get('/user', function(Request $request) {
return $request->user();
});
|
群組路由也行:
1
2
3
4
5
|
Route::middleware('auth:api')->group(function () {
Route::get('/user', function(Request $request) {
return $request->user();
});
}
|
設定 AJAX request 時自動加入 API token
預設情況下,只要在將 API token 塞進 query string 或 HTML form 就可以正常運作,可以參考官方的範例。
但我自己比較喜歡將 token 放到 HTTP request header,而另一種使用方式為 Bearer Token,實作方式是將 Bearer
這個關鍵字與 API token 一起放進 Authorization
這組 HTTP request header,並且以一個空白字元做為區隔,長得像這樣子:
1
2
3
4
5
6
|
$response = $client->request('POST', '/api/user', [
'headers' => [
'Authorization' => 'Bearer '.$token,
'Accept' => 'application/json',
],
]);
|
這裡使用 axios 作為範例。首先,將 API token 以 HTML meta 的形式置入模板的 head 區塊
1
2
|
<meta name="api-token" content="{{ Auth::user()->api_token }}">
<meta name="csrf-token" content="{{ csrf_token() }}">
|
修改 resources/js/bootstrap.js
,在 CSRF token 這段,Laravel 預設是長這樣子:
1
2
3
4
5
6
7
|
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}
|
仿照一樣的形式,加入 API token,並將 CSRF token 的變數名稱修改為 $csrfToken
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
let apiToken = document.head.querySelector('meta[name="api-token"]');
let csrfToken = document.head.querySelector('meta[name="csrf-token"]');
if (apiToken) {
window.axios.defaults.headers.common['Authorization'] = `Bearer ${apiToken.content}`;
} else {
console.error('API token not found.');
}
if (csrfToken) {
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = csrfToken.content;
} else {
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}
|
最後使用 npm
重新編譯,全部就大功告成了。
觀察 HTTP request header,API token 已被塞進 Authorization
一併發送給後端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
Host: localhost:8000
Connection: keep-alive
Accept: application/json, text/plain, */*
DNT: 1
X-XSRF-TOKEN: ed5uK2VqekJMaWxuVXoRVlIjoib2M4zVL01iTEZ4TU1ISlZjYFqVHJclwvZkxpT1wvTTBNI6IjlmODVmZIyZDU4NjZjZTY1Y2ZkYjFiZTFjNlYTVhNmE3Zjc3ZDifQ==
X-CSRF-TOKEN: L2Y5uUsKlz0JBQO6t4fNcUWw9dfRKNkGGhqVBRrW
X-Requested-With: XMLHttpRequest
Authorization: Bearer EjzimAAL6is1lgZZhczQGOdDbtjQyRaJTphPW2jj7cVhDhva8GhJNsU5GI94bkQKWqYLG9vjkXKstKfs
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:8000/check-benchmark
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: jweToken=%7B%22protected%22%3A%22eyJhbGciOiJSU0EiOiJBMjU2R0NNIn0%22%2C%22aad%22%3A%22eyJleHAiOiIyMDIwLTtMTdUMDY6NDU6MjVaIn0%22%2C%22encrypteEV_HYsAkuV5blzfi5CQ8VPbhvz_83xI1jokmRMOqSlQUcYsMfxjJkUkBu-HEGwY76qDrfzxxbBf5qj3ZN1okrBx6KCNq183SJ9hjQbdg9BYx2POPwDC1OjiaX0QiONJCFiMZVVeNdYPn7obBs%22%2C%22tag%22%3A%22JJ7EWL54bTIdR0Y6tgxyhg%2
|
References
- API Authentication - Laravel - The PHP Framework For Web Artisans
- Laravel 自带的 API 守卫驱动 token 使用详解 | Laravel China 社区