【Laravel】#02 tymon/jwt-authの導入

APIを作成するにあたり、必ず必要になるのが認証機能です。
Laravelでは標準でトークン認証が用意されていますが、実際のAPIアプリケーションでよく利用されているのはJWT認証です。本記事ではJWT認証を提供するtymon/jwt-auth パッケージを利用したいと思います。

前回の記事はこちらです。

目次

APIルート

Laravel11にはAPIルートがないため、以下コマンドでAPIルーティングを有効にします。

sail artisan install:api

tymon/jwt-authのインストール

下記コマンドでtymon/jwt-authパッケージをインストールして、vendor:publish コマンドで設定ファイルを作成します。

sail composer require tymon/jwt-auth
sail artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

次に秘密鍵を作成します。
以下コマンド実行後に.envファイルにJWT_SECRET=ランダム文字列が追加されます。

sail artisan jwt:secret

tymon/jwt-authの利用準備

jwt-authを利用するために、Tymon\JWTAuth\Contracts\JWTSubject インターフェースを実装しておく必要があります。以下の通り、app/User.php を修正します。

<?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 Tymon\JWTAuth\Contracts\JWTSubject;

class User extends Authenticatable implements JWTSubject
{
    use HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
        ];
    }

    /**
     * @return mixed
     */
    public function getJWTIdentifier(): mixed
    {
        return $this->getKey();
    }

    /**
     * @return array
     */
    public function getJWTCustomClaims(): array
    {
        return [];
    }
}

アプリケーション内で利用するため、config/auth.php にパッケージ登録されているjwt認証ドライバを追加します。

    'defaults' => [
        'guard' => 'api',
        'passwords' => 'users',
    ],
    'guards' => [
        'api' => [
            'driver' => 'jwt',
            'provider' => 'users',
        ],
    ],

テストデータを作成

この後認証APIを実装するにあたりあらかじめテストデータを用意しておきます。

下記コマンドでSeederを作成します。
Laravel においてSeederとはデータベースにテストデータを一斉に挿入する処理を指します。

sail artisan make:seeder UserSeeder

UserSeeder.php を下記のように修正します。

<?php

namespace Database\Seeders;

use App\Models\User;
use Illuminate\Database\Seeder;

class UserSeeder extends Seeder
{
    /**
     * @return void
     */
    public function run(): void
    {
        User::factory()->create();
    }
}

次にDatabaseSeeder.php を修正します。

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * @return void
     */
    public function run(): void
    {
        $this->call([
            UserSeeder::class
        ]);
    }
}

UserFactory.php を確認するとランダムにユーザー情報を作成するコードが記述されています。
FactoryとはSeederの一種で同テーブルに一度に大量のデータを投入したいときに利用する仕組みです。
そのため、下記コマンドでデータを挿入します。

sail artisan db:seed

データベースを確認すると下記の通りデータが挿入されたことがわかります。

$ sail mysql
mysql> select * from task_db.users;
+----+------------+-----------------------+---------------------+--------------------------------------------------------------+----------------+---------------------+---------------------+
| id | name       | email                 | email_verified_at   | password                                                     | remember_token | created_at          | updated_at          |
+----+------------+-----------------------+---------------------+--------------------------------------------------------------+----------------+---------------------+---------------------+
|  1 | 村山 稔    | fmiyazawa@example.org | 2024-05-17 17:07:19 | $2y$12$u70OUHzPLOKil4AMRa1t0OZRVIwL8E/GEY53cZCvGITwkRmeLAqXO | tMfowrqlu5     | 2024-05-17 17:07:19 | 2024-05-17 17:07:19 |
+----+------------+-----------------------+---------------------+--------------------------------------------------------------+----------------+---------------------+---------------------+
1 row in set (0.00 sec)

LoginControllerを作成

tymon/jwt-authの利用準備とテストデータの作成が終わったので、認証用のコントローラーを作成します。

とその前にLaravelではAPIを実行する際に必ずURLにapiとprefixをつける決まりになっていますが、本アプリケーションはAPIのみで画面は別途用意する想定のため、prefixを削除します。
prefixを削除するにはbootstrap/app.php を下記のように修正します。(apiPrefix: '', を追加します)

    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
        apiPrefix: '',
    )

下記コマンドでLoginController を作成します。
またroutes/api.php にルーティングを記述します。

sail artisan make:controller V1/Auth/AuthBaseController --api
sail artisan make:controller V1/Auth/LoginController --api
<?php

namespace App\Http\Controllers\V1\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;

class AuthBaseController extends Controller
{
    /**
     * @param string $token
     * @return JsonResponse
     */
    protected function respondWithToken(string $token): JsonResponse
    {
        return response()->json([
            'access_token' => $token,
            'token_type' => 'bearer',
            'expires_in' => auth('api')->factory()->getTTL() * 60
        ]);
    }
}
<?php

namespace App\Http\Controllers\V1\Auth;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class LoginController extends AuthBaseController
{
    /**
     * @param Request $request
     * @return JsonResponse
     */
    public function store(Request $request): JsonResponse
    {
        $token = auth()->attempt([
            'email' => $request->get('email'),
            'password' => $request->get('password')
        ]);
        if (!$token) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }
        return $this->respondWithToken($token);
    }
}

routes/api.php にLoginControllerへのルーティングを追加します。

<?php

use App\Http\Controllers\V1\Auth\LoginController;
use Illuminate\Support\Facades\Route;

Route::prefix('v1')->group(function () {
    Route::group([
        'middleware' => 'api',
        'prefix' => 'auth'
    ], function () {
        Route::post('/login', [LoginController::class, 'store'])->name('login');
    });
});

動作確認

実装したコントローラーに以下のコマンドでルクエストを送信してaccess_tokenを作成します。

curl -X POST 'http://localhost/v1/auth/login' \
  -H 'content-type: application/json' \
  -d '{
    "email": "fmiyazawa@example.org",
    "password": "password"
  }'

リクエスト送信後、以下のレスポンスが返却されます。

{
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0L3YxL2F1dGgvbG9naW4iLCJpYXQiOjE3MTU5MzUwNzgsImV4cCI6MTcxNTkzODY3OCwibmJmIjoxNzE1OTM1MDc4LCJqdGkiOiJrWmdWV242NlhQaHEyeHhTIiwic3ViIjoiMSIsInBydiI6IjIzYmQ1Yzg5NDlmNjAwYWRiMzllNzAxYzQwMDg3MmRiN2E1OTc2ZjcifQ.RAFSjRX1gWfxkr90YdYd9Ct4cODJYcjZAo1xx8MZk70",
    "token_type": "bearer",
    "expires_in": 3600
}
よかったらシェアしてね!
  • URLをコピーしました!

コメント

コメントする

目次