Laravel7.xにjwt-authを実装してAPIにはJWTガードを使用しつつ、
通常のweb認証にはsessionガードを使用する方法を、自分の備忘録として残しておきます。
Contents
事前準備
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');
}
}
}
お疲れさまでした。
上手く動きましたでしょうか...?