まえおき
認証って色々種類があって何を使うべきなのか迷いますよね...
・Laravel Passport
・Sanctum
・laravel/ui --auth
・Firebase Authentication
・AWS Cognito
・JWT-Auth
etc...
その中でも今回はJWT-Authを導入してみます。
Laravel+VueSPAのリポジトリ分離パターンや
Laravel+ReactNative等のモバイルに使えるかと思います
Laravel+Bladeの場合は使う意味はないです
JWT-Auth設定
JWT-Authインストール
composer require tymon/jwt-auth
設定ファイルをpublish
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
JWT-Authの設定ファイル(config/jwt.php)を生成
TTLを設定したい場合はこのファイルを見てみると良いです
シークレットキー発行
php artisan jwt:secret
.envに暗号化する際に使用するシークレットキーが定義される
Usersモデル編集
<?php
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Tymon\JWTAuth\Contracts\JWTSubject; // 追加
class User extends Authenticatable implements JWTSubject // 追加
{
use Notifiable;
protected $fillable = [
'name', 'email', 'password',
];
protected $hidden = [
'password', 'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
];
public function getJWTIdentifier() // 追加
{
return $this->getKey();
}
public function getJWTCustomClaims() // 追加
{
return [];
}
}
追加した2つのメソッドは定義しないとエラーになってしまう
(JWTSubjectがインターフェースなので実装する必要がある)
認証ガード変更
<?php
return [
'defaults' => [
'guard' => 'api', // 変更
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'jwt', // 変更
'provider' => 'users',
'hash' => false,
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
'throttle' => 60,
],
],
'password_timeout' => 10800,
];
認証API作成
Route定義
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::group(['middleware' => 'api'], function () {
Route::post('/login', 'AuthController@login');
Route::post('/register', 'AuthController@register');
Route::post('/refresh', 'AuthController@refresh');
});
Route::group(['middleware' => 'auth:api'], function () {
Route::post('/logout', 'AuthController@logout');
Route::post('/mypage', 'MypageController@index');
});
login, registerは認証する必要ないのでmiddlewareはapiのみ
mypageはログインしていないと閲覧ができないdashboardてきなものです
AuthController作成
php artisan make:controller AuthController
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use App\User;
class AuthController extends Controller
{
public function login(Request $request)
{
$validator = Validator::make($request->only(["email", "password"]), [
'email' => 'required|email',
'password' => 'required|string',
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
if (!$token = auth()->attempt($validator->validated())) {
return response()->json(['error' => 'Unauthorized'], 401);
}
return response()->json(['token' => $token]);
}
public function register(Request $request)
{
$validator = Validator::make($request->only(["name", "email", "password", "password_confirmation"]), [
'name' => 'required|max:10|unique:users',
'email' => 'required|email|unique:users|max:50',
'password' => 'required|confirmed|string|min:6|max:30',
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
$user = User::create(array_merge(
$validator->validated(),
['password' => bcrypt($request->password)]
));
return response()->json([
'message' => 'success'
], 201);
}
public function logout()
{
auth()->logout();
return response()->json(['message' => 'success']);
}
public function refresh()
{
$token = auth()->refresh();
return response()->json(['token' => $token]);
}
}
ログイン成功時にtokenだけでなくユーザー情報も返してフロントで保持しておく場合、
$name = auth()->user()->name
上記の様にユーザー情報(上の例はname)を取得することができるのでresponse()->json内で記述してあげればよいです
MypageController作成
php artisan make:controller MypageController
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class MypageController extends Controller
{
public function index()
{
return response()->json(auth()->user());
}
}
認証に成功したユーザー(ログイン中)の情報をただ返しているだけです...
POSTMANでAPI検証
新規登録
ログイン
マイページ(未認証時)
ちゃんと401が返ってきています
※HeadersにAccept application/jsonを追加しないとloginルートにリダイレクトしようとします
マイページ(認証済 トークン付与)
POSTMANでトークンをヘッダーに含めるにはAuthorizationタブから設定します
POSTMANのバージョンによってやり方が異なる場合があるので注意
これで基本的な認証システムが完成しました
おまけ
JWT_TTL, JWT_REFRESH_TTLとは
JWT_TTL=トークンの有効期限
デフォルト: 60分
JWT_REFRESH_TTL=トークンを更新できる有効期限
デフォルト: 2週間
上記のデフォルト設定値の場合
アクセストークンの有効期限(60分)が切れてしまうと middleware api:authを指定しているルートは401を返す
有効期限切れのトークンを使用して新しいトークンの発行(refresh)が2週間-60分の間であれば可能
60分経過する度にログインを毎回させるのはユーザーのストレスでしかないので
うまくトークンを更新するロジックをフロントで実装する必要があります
(例えば認証が必要なAPIから401が返ってきた場合、自動でトークンの更新を試行するようにします)
APIのURIプレフィックス変更 (api→v1)
protected function mapApiRoutes()
{
Route::prefix('v1') // 変更
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
デフォルトだと「test.com/api/login」の様に"api"が自動的にURIの先頭に付与されます
これだとAPIサーバーをサブドメインで切っている(api.test.com/loginの様な)と困るので上記で解決
v1,v2とかのAPIバージョニングを表すプレフィックスはrouteファイルで設定しても勿論良い
ローカル開発環境時のChromeブラウザによるCORSエラー
axiosでBASE_URLを設定する際にlocalhost:8000/apiだとCORSエラーが発生してしまうので
下記の様に設定すると発生しなくなる
NODE_ENV='development'
VUE_APP_API_BASE_URL='http://127.0.0.1:8000/api'