Laravel + tymon/jwt-auth にて認証つきAPIサーバーを構築する
いろいろな情報を参照させて頂き試したが、AccessTokenが切れた状態 & RefreshTokenの期限が切れていない状態でのトークンの更新ができなかったので、少し工夫したところも記載しています。
※Laravel 8.6.0にて、routes/api.phpの書き方が変わっていたので追記しました。
なお、ここで構築した環境は以下にてアクセスできますのでご参照ください。
GitHub: https://github.com/sankosc/apitest-server
サーバー: https://develop.sankosc.co.jp/apitest/
環境
- PHP 7.4.5
- Laravel Framework 7.10.3
- 10.4.11-MariaDB
- composerとnpmはインストール済み
準備
Laravelのインストール
$ composer create-project laravel/laravel [プロジェクト名] --prefer-dist
データベース作成
- PhpMyAdminで作成 ( utf8mb4_bin )
- .env編集
DB_DATABASE=[データベース名]
Laravelの認証機能を有効にする
$ composer require laravel/ui
$ php artisan ui vue --auth
$ npm install
$ npm run dev
マイグレーション
$ php artisan migrate
認証API構築
JWT認証ライブラリインポート
$ composer require tymon/jwt-auth
$ php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
$ php artisan jwt:secret
ユーザーモデル編集
<?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
{
//
// ~~ 省略 ~~
//
/**
* Get the identifier that will be stored in the subject claim of the JWT.
*
* @return mixed
*/
public function getJWTIdentifier()
{
return $this->getKey();
}
/**
* Return a key value array, containing any custom claims to be added to the JWT.
*
* @return array
*/
public function getJWTCustomClaims()
{
return [];
}
}
config/auth.phpを編集
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
route追加
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::post('login', 'Api\AuthController@login');
Route::get('refresh', 'Api\AuthController@refresh');
Route::group(['middleware' => ['jwt.auth']], function () {
Route::post('logout', 'Api\AuthController@logout');
Route::get('me', 'Api\AuthController@me');
});
※Larave 8.6.0
Route::post('login', [App\Http\Controllers\Api\AuthController::class, 'login']);
Route::get('refresh', [App\Http\Controllers\Api\AuthController::class, 'refresh']);
Route::group(['middleware' => ['jwt.auth']], function () {
Route::post('logout', [App\Http\Controllers\Api\AuthController::class, 'logout']);
Route::get('me', [App\Http\Controllers\Api\AuthController::class, 'me']);
});
AuthController作成
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use Illuminate\Auth\Events\Registered;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;
class AuthController extends Controller
{
/**
* Create a new AuthController instance.
*
* @return void
*/
public function __construct()
{
// $this->middleware('auth:api', ['except' => ['login']]);
}
/**
* Get a JWT via given credentials.
*
* @return \Illuminate\Http\JsonResponse
*/
public function login()
{
$credentials = request(['email', 'password']);
if (! $token = auth()->attempt($credentials)) {
return response()->json(['error' => 'Unauthorized'], 401);
}
return $this->respondWithToken($token);
}
/**
* Get the authenticated User.
*
* @return \Illuminate\Http\JsonResponse
*/
public function me()
{
return response()->json(auth()->user());
}
/**
* Log the user out (Invalidate the token).
*
* @return \Illuminate\Http\JsonResponse
*/
public function logout()
{
auth()->logout();
return response()->json(['message' => 'Successfully logged out']);
}
/**
* Refresh a token.
*
* @return \Illuminate\Http\JsonResponse
*/
public function refresh()
{
try {
return $this->respondWithToken(auth()->refresh());
} catch (\Tymon\JWTAuth\Exceptions\JWTException $e) {
return response()->json(['error' => 'Unauthorized'], 401);
}
}
/**
* Get the token array structure.
*
* @param string $token
*
* @return \Illuminate\Http\JsonResponse
*/
protected function respondWithToken($token)
{
return response()->json([
'access_token' => $token,
'token_type' => 'bearer',
'expires_in' => auth()->factory()->getTTL() * 60
]);
}
}
確認
Invoke-RestMethod -Uri "http://localhost/api/login" -Method POST -Body "email=sample@sankosc.co.jp&password=sample123"
テスト用のAPI作成
認証なしのget API helloとpost API echoを作成します。
コントローラ作成
<?php
namespace App\Http\Controllers\APi;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class TestController extends Controller
{
public function hello()
{
return response()->json([
'message' => 'hello'
]);
}
public function echo(Request $request)
{
return response()->json([
'message' => $request->input('message')
]);
}
}
route追加
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::post('login', 'Api\AuthController@login');
Route::get('hello', 'Api\TestController@hello');
Route::post('echo', 'Api\TestController@echo');
Route::get('refresh', 'Api\AuthController@refresh');
Route::group(['middleware' => ['jwt.auth']], function () {
Route::post('logout', 'Api\AuthController@logout');
Route::get('me', 'Api\AuthController@me');
});
AccessTokenのRefreshについて
AccessTokenが切れた状態かつ、RefreshTokenの期限が切れていない状態でのトークンの更新する方法がどうしてもわかりませんでした。
jwt.refresh
をroutes/api.phpにて設定するように思えるのですがうまく動作しません。
なので、公式のQuick startで紹介されている方法から以下の2点を変更しています。
- route/api.phpにてrefreshを認証の外におく
- AuthControllerにてrefreshの処理を少し修正
Route::post('login', 'Api\AuthController@login');
Route::get('hello', 'Api\TestController@hello');
Route::post('echo', 'Api\TestController@echo');
Route::group(['middleware' => ['jwt.auth']], function () {
Route::post('logout', 'Api\AuthController@logout');
Route::get('refresh', 'Api\AuthController@refresh');
Route::get('me', 'Api\AuthController@me');
});
Route::post('login', 'Api\AuthController@login');
Route::get('hello', 'Api\TestController@hello');
Route::post('echo', 'Api\TestController@echo');
Route::get('refresh', 'Api\AuthController@refresh');
Route::group(['middleware' => ['jwt.auth']], function () {
Route::post('logout', 'Api\AuthController@logout');
Route::get('me', 'Api\AuthController@me');
});
AuthController.refresh修正
public function refresh()
{
return $this->respondWithToken(auth()->refresh());
}
AuthController.refresh修正
public function refresh()
{
try {
return $this->respondWithToken(auth()->refresh());
} catch (\Tymon\JWTAuth\Exceptions\JWTException $e) {
return response()->json(['error' => 'Unauthorized'], 401);
}
}
こうすることで、完全に認証がない状態だと401を返し、RefreshTokenの期限が切れる前であれば、更新したトークン返すようになります。