Laravel

Laravel7でtymon/jwt-authによるJWT認証を実装する

Laravel7.xにjwt-authを実装してAPIにはJWTガードを使用しつつ、
通常のweb認証にはsessionガードを使用する方法を、自分の備忘録として残しておきます。

事前準備

Laravelに標準で準備されている認証を、事前にインストールしておきます。

composer require laravel/ui

php artisan ui vue --auth

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

インストール時には、必ずバージョンの指定を行いましょう。

Laravel7.xにはバージョン「1.0.0」を指定すれば良いようです。

composer require tymon/jwt-auth

設定ファイルをコピー(publish)する

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

これにより、プロジェクトにconfig/jwt.phpが追加されますので、ここで各種設定ができます。

このファイル内にもあるように、デフォルトのトークン有効期限は(JWT_TTL)発行から1時間、トークンリフレッシュの有効期限(JWT_REFRESH_TTL)は発行から2週間が設定されていますが、これらは.envファイルにて調整が可能です。

秘密鍵の生成

以下のコマンドで、.envに秘密鍵JWT_SECRETが追記されます。

php artisan jwt:secret

Userモデルをカスタマイズする

Userモデルに、getJWTIdentifier()getJWTCustomClaims()の2つのメソッドを追加する必要があります。

<?php

namespace App;

use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable implements JWTSubject
{
    use Notifiable;

    ...

    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

    public function getJWTCustomClaims()
    {
        return [];
    }
}

認証ガードの設定

今回は、通常のweb認証は残しておきたいため、defaultsはそのままに、guards > apiの設定のみ調整します。

<?php

return [

    ...

    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],

    ...

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'api' => [
            'driver' => 'jwt',
            'provider' => 'users',
            'hash' => false,
        ],
    ],

コントローラの作成

以下のコマンドで、JWT認証のためのコントローラを作成します。

php artisan make:controller AuthenticationController

コントローラは、公式のドキュメントを参考に、一部をカスタマイズしながら実装しました。

最終的には、次のように実装しました。
公式のドキュメント通りauth()としてしまうと、デフォルトであるwebが使用されてしまうため、ここではapiを明示的に指定しています。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;
use Tymon\JWTAuth\Exceptions\JWTException;

class AuthenticationController extends Controller
{
    function login(Request $request) {
        $credentials = $request->only('email', 'password');

        try {
            if (! $token = auth('api')->attempt($credentials)) {
                return response()->json(['error' => 'Unauthorized'], 401);
            }
        } catch (JWTException $e) {
            return response()->json(['error' => 'Could not create token'], 500);
        }

        return $this->respondWithToken($token);
    }

    function me()
    {
        return response()->json(auth('api')->user());
    }

    function logout()
    {
        auth('api')->logout();

        return response()->json(['message' => 'Successfully logged out']);
    }

    function refresh()
    {
        try {
            if (! $token = auth('api')->refresh()) {
                return response()->json(['error' => 'Unauthorized'], 401);
            }
        } catch (JWTException $e) {
            return response()->json(['error' => 'Could not create token'], 500);
        }

        return $this->respondWithToken($token);
    }

    function unauthorized() {
        return response()->json(['error' => 'Unauthorized'], 401);
    }

    protected function respondWithToken($token)
    {
        return response()->json([
            'token' => $token,
        ]);
    }
}

ルーティングの設定

ルーティングは次のように設定します。
/api/auth/refreshのエンドポイントのみ、ミドルウェアをguest:apiとしてしまうと上手くいかないようで...しばらくはまりました。。

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::prefix('/auth')->group(function () {
    Route::group(['middleware' => 'api'], function () {
        Route::post('/login',        'AuthenticationController@login')       ->name('api.login');
        Route::post('/refresh',      'AuthenticationController@refresh')     ->name('api.refresh');
        Route::get ('/unauthorized', 'AuthenticationController@unauthorized')->name('api.unauthorized');
    });
    Route::group(['middleware' => 'auth:api'], function () {
        Route::get ('/me',     'AuthenticationController@me')    ->name('api.me');
        Route::post('/logout', 'AuthenticationController@logout')->name('api.logout');
    });
});

認証に失敗した際の応答を調整

最後に、Authenticateミドルウェアも調整して、完成です。

<?php

namespace App\Http\Middleware;

use Illuminate\Auth\Middleware\Authenticate as Middleware;

class Authenticate extends Middleware
{
    /**
     * Get the path the user should be redirected to when they are not authenticated.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return string|null
     */
    protected function redirectTo($request)
    {
        if (! $request->expectsJson()) {
            if ($request->is('api/*')) {
                return route('api.unauthorized');
            }
            return route('login');
        }
    }
}

お疲れさまでした。
上手く動きましたでしょうか...?

-Laravel
-, ,

© 2020 teckmemo