Laravel Sanctumとは
SPA、モバイルアプリケーション、トークンベースAPIを提供する認証パッケージになります。
Laravelでトークン認証を実装する場合はLaravel Passport か Laravel Sanctumがあり、
両社の比較表は下記になります。OAuth2を利用したい場合はLaravel Passportで実装できます。
特徴 | Laravel Sanctum | Laravel Passport |
---|---|---|
主な機能 | SPA認証 APIトークン認証 ※OAuth2.0はサポートしていない | OAuth2.0 |
メリット、デメリット | ・実装がシンプル ・リソース消費が少ない ・軽量なパッケージ ・セキュリティは標準的(CSRF保護有) | ・実装が複雑 ・リソース消費が大きい ・機能が豊富で柔軟なカスタマイズ性 ・OAuth2.0準拠のため高いセキュリティ |
推奨規模 | 小、中規模なシステム向け | 大規模なシステム向け |
SanctumにはAPIトークン認証とSPA認証の2種類あります。
APIトークン認証
AuthorizationヘッダのBearerトークンをサーバ側で保持している値と照合することで、有効なユーザであることを判定します。
SPA認証
Laravelのセッション管理機能を利用した認証で、Cookieベースのセッション認証です。
CSRF対策として、ログイン時にCSRFトークン(XSRF-TOKEN)を付与しCookieに格納します。
サーバ側でCSRFトークンとセッションIDを照合することで、セッションハイジャック対策をしています。
SanctumではCookieにHttpOnly 属性を付与しているため、JavaScriptからセッションIDを読み取ることはできません。また、CSRFトークンとSameSite 属性にはLaxを設定しているためクロスサイトへのCookie送信はある程度防ぐことができます。
APIトークン認証の実装
設定ファイルが少なく実装が楽なのでAPIトークン認証を実装します。
バージョン |
---|
“laravel/framework”: “^11.0” |
“laravel/sanctum”: “^4.0” |
Sunctumインストール
artisanコマンドでSunctumをインストールします。
php artisan install:api
デフォルトで下記マイグレーションファイルが用意されているためそのまま利用します。
database/migrations/yyyy_mm_dd_000001_personal_access_tokens.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('personal_access_tokens', function (Blueprint $table) {
$table->id();
$table->morphs('tokenable');
$table->string('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamp('expires_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('personal_access_tokens');
}
};
トークン認証利用設定
APIトークン認証ではHasApiTokensトレイトを使用します。
認証用のModelsにHasApiTokensを追加します。
app\Models\User.php
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens;
Sanctumの設定ファイルに有効期限を定義します。
ここでは60分で設定しています。
config\sanctum.php
/*
|--------------------------------------------------------------------------
| Expiration Minutes
|--------------------------------------------------------------------------
|
| This value controls the number of minutes until an issued token will be
| considered expired. This will override any values set in the token's
| "expires_at" attribute, but first-party sessions are not affected.
|
*/
'expiration' => 60,
APIトークン認証で利用するコントローラをartisanコマンドで作成します。
php artisan make:controller AuthApiController
コントローラにログイン、ログアウト、ユーザ情報を取得できるメソッドを追加します。
app\Http\Controllerss\AuthApiController.php
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Carbon\Carbon;
class ApiController extends Controller
{
public function login(Request $request)
{
$validator = Validator::make($request->all(), [
'email' => ['required', 'email'],
'password' => ['required']
]);
if ($validator->fails()) {
return response()->json($validator->messages(), Response::HTTP_UNPROCESSABLE_ENTITY);
}
if (Auth::attempt(['email' => $request->email, 'password' => $request->password])) {
$user = User::where('email', $request->email)->first();
$user->tokens()->delete();
$token = $user->createToken('AccessToken',["*"],Carbon::now()->addMinutes(config('sanctum.expiration')))->plainTextToken;
return response()->json(['token' => $token], Response::HTTP_OK);
} else {
return response()->json('User Unauthorized', Response::HTTP_UNAUTHORIZED);
}
}
public function user(Request $request){
return response()->json(
[
$request->user()->name,
$request->user()->email,
]
);
}
public function logout(Request $request)
{
$request->user()->currentAccessToken()->delete();
return response()->json(['message' => 'Logout Success!'], Response::HTTP_OK);
}
}
api.phpにルートを記載します。
ミドルウェアでSanctum認証ガードをアタッチすることでログイン済みであるかを判定します。
route\api.php
use App\Http\Controllers\AuthApiController;
Route::post('/login', [AuthApiController::class, 'login'])->name('login');
Route::group(['middleware' => ['auth:sanctum']], function () {
Route::get('/user', [AuthApiController::class, 'user']);
Route::get('/logout', [AuthApiController::class, 'logout']);
});
APIトークン確認
curlコマンドでトークンを取得できることを確認します。
-iを指定することでレスポンスヘッダとボディ全ての表示が可能です。
# curl -i -X POST -H "Content-Type: application/json" -d '{"email":"test@mail.com", "password":"12345678"}' https://localhost/api/login --insecure
HTTP/2 200
server: nginx/1.25.1
content-type: application/json
x-powered-by: PHP/8.2.8
cache-control: no-cache, private
date: Thu, 10 Nov 2024 5:50:54 GMT
x-ratelimit-limit: 60
x-ratelimit-remaining: 58
access-control-allow-origin: *
x-frame-options: SAMEORIGIN
x-xss-protection: 1; mode=block
{"token":"1|xaNGj7JOig4jJgQ7xeSyWFFtmHlxUpa3mfUdXeQa908a7e53"}
認証成功時は下記のようにDBへ登録されます。
+----+-----------------+--------------+-------------+------------------------------------------------------------------+-----------+--------------+---------------------+---------------------+---------------------+
| id | tokenable_type | tokenable_id | name | token | abilities | last_used_at | expires_at | created_at | updated_at |
+----+-----------------+--------------+-------------+------------------------------------------------------------------+-----------+--------------+---------------------+---------------------+---------------------+
| 1 | App\Models\User | 2 | AccessToken | 83fcb2017389e8d35bd4802248a590f5027f801075525a81f9b572201fff9ecd | ["*"] | NULL | 2024-11-10 06:17:47 | 2024-11-10 05:17:47 | 2024-11-10 05:17:47 |
+----+-----------------+--------------+-------------+------------------------------------------------------------------+-----------+--------------+---------------------+---------------------+---------------------+
AuthorizationヘッダにBearerトークンを指定することで、ユーザ情報の取得を確認します。
# curl -X GET -H "Authorization:Bearer 1|xaNGj7JOig4jJgQ7xeSyWFFtmHlxUpa3mfUdXeQa908a7e53" "Content-Type:application/json" https://localhost/api/user --insecure
["user001","test@mail.com"]
有効期限切れトークンの削除
デフォルトでは有効期限が切れたトークンは削除されません。
php artisan sanctum:prune-expiredコマンドを手動実行することでDBから削除することが可能です。
php artisan sanctum:prune-expired
Laravel 11であればScheduleタスクを登録することが可能です。
ここでは24時間間隔で有効期限切れトークンを削除しています。
config\sanctum.php
use Illuminate\Support\Facades\Schedule;
Schedule::command('sanctum:prune-expired --hours=24')->daily();
php artisan schedule:listコマンドでスケジュール一覧を確認できます。
# php artisan schedule:list
0 0 * * * php artisan sanctum:prune-expired --hours=24 ..................... Next Due: 44分後
最後に
ネイティブなスマホアプリの認証について考えて構築してみました。
ただデフォルトの状態ではセキュリティが低く、IPアドレスでレート制限を実施してくれないみたいです。
なので本番構築する際はレート制限のカスタマイズやWAFの導入をした方が良さそうですね。