5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

tymon/jwt-auth を拡張するパッケージを公開して不満が解消した

Last updated at Posted at 2020-04-26

はじめに

LaravelでAPIサーバーを開発する際、認証にJWTを使うことが増えてきました。
ライブラリには決まって、tymon/jwt-authを使うのですが、いくつか不満があって、毎回カスタマイズしていたので、拡張ライブラリを作って公開しました。

tymon/jwt-authに対する不満

クエリパラメータ、リクエストボディにあるtokenもJWTトークンだと認識してしまう

Laravelで作ったらすべて解決ではないよ?」という記事でも書いたのですが、tymon/jwt-authは、クエリパラメータ、リクエストボディにあるtokenもJWTトークンだと認識してしまいます。
正直、QueryStringInputSourceRouteParamsは使うことないのでは?と考えてます。

vendor/tymon/jwt-auth/src/Providers/AbstractServiceProvider.php
    protected function registerTokenParser()
    {
        $this->app->singleton('tymon.jwt.parser', function ($app) {
            $parser = new Parser(
                $app['request'],
                [
                    new AuthHeaders,
                    new QueryString,
                    new InputSource,
                    new RouteParams,
                    new Cookies($this->config('decrypt_cookies')),
                ]
            );

            $app->refresh('request', $parser, 'setRequest');

            return $parser;
        });
    }

'token'という名前を設定ファイルから変更できない

ParsersetKey関数で名前を変更することはできますが、できれば、設定ファイルで環境ごとに変えられるようにしたいですよね?
Add ability to provide custom cookie name from config by SiebeVE · Pull Request #1933でも、setKeyする方法が紹介されていますが、設定ファイルに切り出す気はなさそうです。

vendor/tymon/jwt-auth/src/Http/Parser/KeyTrait.php
namespace Tymon\JWTAuth\Http\Parser;

trait KeyTrait
{
    /**
     * The key.
     *
     * @var string
     */
    protected $key = 'token';

    /**
     * Set the key.
     *
     * @param  string  $key
     *
     * @return $this
     */
    public function setKey($key)
    {
        $this->key = $key;

        return $this;
    }

'tymon.jwt.parser'を拡張

以上を踏まえて、imunew/tymon-jwt-authでは、下記のように、拡張しました。
QueryStringInputSourceRouteParamsは削除。
Cookiesだけにして、AuthHeadersは任意で追加可能にしました。

src/Providers/ServiceProvider.php

    private function resetParserChain()
    {
        $this->app->extend('tymon.jwt.parser', function (Parser $parser) {
            $chain = [
                (new Cookies(config('jwt.decrypt_cookies')))
                    ->setKey(config('jwt-auth.cookie.key'))
            ];
            if (config('jwt-auth.auth-headers.enabled')) {
                $chain[] = new AuthHeaders();
            }
            return $parser->setChain($chain);
        });
    }
config/jwt-auth.php
return [
    "cookie" => [
        'key' => env('JWT_AUTH_COOKIE_KEY', 'auth-token')
    ],
    "auth-header" => [
        'enabled' => env('JWT_AUTH_AUTH_HEADER_ENABLED', false)
    ]
];

追加機能

今回、'tymon.jwt.parser'を拡張しただけではなく、新たに2つの機能を追加しました。

AuthResource

ログインAPIが返すリソースとして使えそうな、API Resourceを実装しました。
JWTトークンをCookieにセットします。

src/Resources/AuthResource.php
class AuthResource extends JsonResource
{
    /** @var string|null */
    public static $wrap = null;

    /**
     * {@inheritDoc}
     */
    public function toArray($request)
    {
        return [];
    }

    /**
     * @param Request $request
     * @return JsonResponse
     */
    public function toResponse($request)
    {
        $response = parent::toResponse($request);
        return HttpHelper::respondWithCookie($request, $response, $this->resource->accessToken);
    }
}

RefreshJwtToken

今回の目玉機能だと思っているんですが、JWTトークンの有効期限が切れたら自動でリフレッシュしてくれるミドルウェアを実装しました。
尚、このミドルウェアのアイデアは、Laravel での tymon/jwt-auth による JWT トークンの自動更新から得ました。
@yh1224 さん、ありがとうございました。

src/Middleware/RefreshJwtToken.php
class RefreshJwtToken extends BaseMiddleware
{
    /**
     * @param Request $request
     * @param Closure $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        $token = null;
        try {
            $token = $this->auth->parseToken();
            $token->authenticate();
        } catch (TokenExpiredException $e) {
            // Token expired: try refresh
            try {
                return $this->refreshJwtToken($token, $request, $next);
            } catch (JWTException $e) {
                // Refresh failed (refresh expired)
            }
        } catch (JWTException $e) {
            // Invalid token
        }
        return $next($request);
    }

    /**
     * @param JWT $jwt
     * @param Request $request
     * @param Closure $next
     * @return JsonResponse|Response
     */
    private function refreshJwtToken(JWT $jwt, Request $request, Closure $next)
    {
        $newToken = $jwt->refresh();
        $request->cookies->set(config('jwt-auth.cookie.key'), $newToken);
        $response = $next($request);
        return HttpHelper::respondWithCookie($request, $response, $newToken);
    }
}

RefreshJwtTokenを有効にするには、以下のように、Kernel.phpに設定を追加します。

app/Http/Kernel.php

    protected $middlewareGroups = [
        'web' => [
            // ...
        ],

        'api' => [
            'throttle:60,1',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
            \Imunew\JWTAuth\Middleware\RefreshJwtToken::class, // ここを追加
        ]
    ];

    protected $middlewarePriority = [
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \Imunew\JWTAuth\Middleware\RefreshJwtToken::class, // ここを追加
        \App\Http\Middleware\Authenticate::class, // この行よりも上に追加する
        \Illuminate\Routing\Middleware\ThrottleRequests::class,
        \Illuminate\Session\Middleware\AuthenticateSession::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
        \Illuminate\Auth\Middleware\Authorize::class,
    ];

$middlewarePriority\App\Http\Middleware\Authenticate::classよりも前に、RefreshJwtTokenを追加しないと、先にAuthenticateが実行されてしまい、401になってしまうので注意してください。

追記(2021-01-14)

Laravel 7.x以降、App\Http\Kernelクラスから$middlewarePriorityが削除されました。
(詳しくは、以下のpull-requestを参照)

対応としては、以下の公式ドキュメントにあるように、基底クラス(Illuminate\Foundation\Http\Kernel\Kernel)からコピーした後、\Imunew\JWTAuth\Middleware\RefreshJwtToken::classを挿入します。

Rarely, you may need your middleware to execute in a specific order but not have control over their order when they are assigned to the route. In this case, you may specify your middleware priority using the $middlewarePriority property of your app/Http/Kernel.php file. This property may not exist in your HTTP kernel by default. If it does not exist, you may copy its default definition below:

    protected $middlewarePriority = [
        \Illuminate\Cookie\Middleware\EncryptCookies::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \Imunew\JWTAuth\Middleware\RefreshJwtToken::class, // ここを追加
        \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
        \Illuminate\Routing\Middleware\ThrottleRequests::class,
        \Illuminate\Session\Middleware\AuthenticateSession::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
        \Illuminate\Auth\Middleware\Authorize::class,
    ];

おわりに

2020-03-05に、ついに、1.0.0がリリースされたtymon/jwt-authですが、githubissuesなど見ていても、Parser周りは特に不満があるかと思います。
そんなときは、imunew/tymon-jwt-authも合わせて、composer requireしていただければ幸いです。
pull-requestsまたはissuesお待ちしております。
ではでは。

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?