資料庫設定

使用 make:migration 建立新的 migration:

php artisan make:migration --table=users add_api_token

加入 api_token 欄位:

public function up()
{
    Schema::table('users', function (Blueprint $table) {
        $table->string('api_token', 80)->after('password')
            ->unique()
            ->nullable()
            ->default(null);
    });
}

執行遷移:

php artisan migrate

產生 token

官方的範例是在使用者註冊時同時產生 token,但如果沒有另外實作更新機制的話可能不是很安全,這邊選擇的實作方式是在使用者登入時重新產生一組 token,這樣使用者不用自己管理 token,也不會永遠使用同一組 token。

使用 make:listener 建立一個 listener:

php artisan make:listener SuccessfulLogin

修改 app/Listeners/SuccessfulLogin.php,加入以下內容,讓事件觸發時更新 API token。

public function handle()
{
    $user = Auth::user();
    $user->api_token = Str::random(80);
    $user->save();
}

最後修改 app/Providers/EventServiceProvider.php,將我們剛剛新增的 listener 綁定到使用者登入的事件:

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

Route::middleware('auth:api')->get('/user', function(Request $request) {
    return $request->user();
});

群組路由也行:

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,並且以一個空白字元做為區隔,長得像這樣子:

$response = $client->request('POST', '/api/user', [
    'headers' => [
        'Authorization' => 'Bearer '.$token,
        'Accept' => 'application/json',
    ],
]);

這裡使用 axios 作為範例。首先,將 API token 以 HTML meta 的形式置入模板:

修改 resources/js/bootstrap.js,在 CSRF token 這段,Laravel 預設是長這樣子:

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:

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 重新編譯,全部就大功告成了。

npm run dev

觀察 HTTP request header,API token 已被塞進 Authorization 一併發送給後端:

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

  1. API Authentication – Laravel – The PHP Framework For Web Artisans
  2. Laravel 自带的 API 守卫驱动 token 使用详解 | Laravel China 社区

留言

這個網站採用 Akismet 服務減少垃圾留言。進一步瞭解 Akismet 如何處理網站訪客的留言資料