Laravel API Token 使用方式

資料庫設定

使用 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 社区
Leave a comment

[PHP] 使用兩個問號 (Null Coalescing Operator) 簡易判斷並賦值

在 PHP 5.3 可以透過 ?: 運算子簡單的判斷並賦值

$foo = $bar ?: $baz;

上面的運算式等同:

$foo = $bar ? $bar : $baz;

但是在沒有宣告變數之前仍然會出現錯誤訊息。而 PHP 7.0 開始支援兩個問號 (??) 判斷並賦值,而且不用事先使用 isset() 判斷變數是否存在:

$username = $_GET['user'] ?? 'nobody';

上面的運算式等同:

$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';

而且可以串一個以上的判斷:

$username = $_GET['user'] ?? $_POST['user'] ?? 'nobody';

References

Leave a comment

[Laravel] 使用 MigrateSpecific 遷移指定檔案到資料庫

當我們將 migration file 遷移到資料庫後,日後想要直接修改原先的 migration file 而不是新增一個去做結構變更時,可以透過 migrate:refresh 重新遷移,但是如果前一次遷移包含多個檔案時,這些資料表都會受到影響;若想要修改的資料表不屬於上一個批次(batch),透過 –step 選項去做遷移,影響範圍就會更大。

常見的做法是建立一個臨時目錄,將 migration file 放進去後執行遷移,但是這個做法僅適用於第一次遷移;另一種方式是修改資料表 migrations 的批次號碼,讓指定的 migration file 成為上一個批次。

這些都是臨時性做法,若是有很多批要修改就會很麻煩,所以我就寫了一個小工具,之後用一行指令就可以解決這個問題。

 

Package Name: MigrateSpecific
Version: 1.2.1
Site: https://github.com/caloskao/migrate-specific

Read more “[Laravel] 使用 MigrateSpecific 遷移指定檔案到資料庫”

Leave a comment

[PHP] 提升 json_encode() 資料可讀性

PHP 可藉由 json_encode()json_decode() 來編碼與解碼 JSON 格式的資料,在一般情況下不需要特別給予任何參數,但如果將 JSON 格式資料直接儲存的話,預設是儲存一行文,在不靠任何工具的前提下僅以肉眼辨識會顯得有點吃力。

$data = [
    '5b8e2e456106f8.12710761' => [
        'id' => '5b8e2e456106f8.12710761',
        'description' => '',
        'options' => [],
        'size' => 1243916,
        'create_at' => '2018-09-04 15:04:02'
    ]
];
echo json_encode($data);

 

輸出如下:

{"5b8e2e456106f8.12710761":{"id":"5b8e2e456106f8.12710761","description":"","options":[],"size":1243916,"create_at":"2018-09-04 15:04:02"}}

 

如果想要提升 JSON 格式資料的可讀性,只需要將 json_encode() 的第二個參數設為 JSON_PRETTY_PRINT 即可。

echo json_encode($data, JSON_PRETTY_PRINT);

 

輸出:

{
    "5b8e2e456106f8.12710761": {
        "id": "5b8e2e456106f8.12710761",
        "description": "",
        "options": [],
        "size": 1243916,
        "create_at": "2018-09-04 15:04:02"
    }
}

 

PHP 5.6.6 以上可選擇的參數共有 11 個,詳細說明可見 PHP 官網 

 

 

 
 
Leave a comment

[PHP] 序列化資料線上編輯器

PHP 可以透過 serialize() 將物件轉換成序列化文字資料後儲存在任何地方,需要使用的時候再透過 unserialize() 進行反序列化後得到原始物件,但因為序列化資料會記錄資料長度,因此如果想直接修改序列化資料的內容,需要連同資料長度也一起算進去,手動修改時麻煩且容易出錯。這時候可以透過 Serialized PHP Editor  這個 PHP 序列化資料線上編輯器進行修改就會方便很多。

Read more “[PHP] 序列化資料線上編輯器”

Leave a comment