はじめに
Laravel
でAPIサーバーを開発する際、認証にJWT
を使うことが増えてきました。
ライブラリには決まって、tymon/jwt-authを使うのですが、いくつか不満があって、毎回カスタマイズしていたので、拡張ライブラリを作って公開しました。
tymon/jwt-auth
に対する不満
クエリパラメータ、リクエストボディにあるtoken
もJWTトークンだと認識してしまう
「Laravelで作ったらすべて解決ではないよ?」という記事でも書いたのですが、tymon/jwt-auth
は、クエリパラメータ、リクエストボディにあるtoken
もJWTトークンだと認識してしまいます。
正直、QueryString
、InputSource
、RouteParams
は使うことないのでは?と考えてます。
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'
という名前を設定ファイルから変更できない
各Parser
のsetKey
関数で名前を変更することはできますが、できれば、設定ファイルで環境ごとに変えられるようにしたいですよね?
Add ability to provide custom cookie name from config by SiebeVE · Pull Request #1933でも、setKey
する方法が紹介されていますが、設定ファイルに切り出す気はなさそうです。
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では、下記のように、拡張しました。
QueryString
、InputSource
、RouteParams
は削除。
Cookies
だけにして、AuthHeaders
は任意で追加可能にしました。
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);
});
}
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
にセットします。
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 さん、ありがとうございました。
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
に設定を追加します。
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
ですが、github
のissues
など見ていても、Parser
周りは特に不満があるかと思います。
そんなときは、imunew/tymon-jwt-authも合わせて、composer require
していただければ幸いです。
pull-requests
またはissues
お待ちしております。
ではでは。