もくじ
備考
- Laravel6ではjwt-auth1以上が必要。
https://jwt-auth.readthedocs.io/en/develop/laravel-installation/ - /logoutは作らない。
アプリ側でtokenを決して貰うので - 当記事の設定を推奨しているわけではないです。
・jwt-authのブラックリスト機能をfalseにしています。
設定例
- アクセストークン有効期限:15分
- リフレッシュトークン有効期限:4週間
jwt-authインストール
# composer require tymon/jwt-auth ^1.0.0
jwt-authをパブリッシュする
$ php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider" Copied File [/vendor/tymon/jwt-auth/config/config.php] To [/config/jwt.php] Publishing complete. Publishing complete.
config/jwt.phpが生成される。
jwt用のキーを生成
# php artisan jwt:secret
.envに生成されている、確認
JWT_SECRET=xxxxxxxxxx
config/app.php
'providers' => [
...
Tymon\JWTAuth\Providers\LaravelServiceProvider::class,
]
configファイルを変更したのでキャッシュクリア
$ php artisan config:clear
/config/auth.php
'defaults' => [
- 'guard' => 'web',
+ 'guard' => 'api',
'passwords' => 'users',
],
・・・
'api' => [
- 'driver' => 'token',
+ 'driver' => 'jwt',
'provider' => 'users',
'hash' => false,
],
app/Http/Kernel.php
protected $routeMiddleware = [ ・・・ + 'jwt_auth' => \Tymon\JWTAuth\Http\Middleware\Authenticate::class, + 'jwt_refresh' => \Tymon\JWTAuth\Http\Middleware\RefreshToken::class,
routes/apiでjwt-authのミドルウェアを利用する場合に、ここに登録しておくと便利。
Models/Userモデルを作成
ModelsディレクトリにUserモデルであるUser.phpを配置するデファクトスタンダードな設定。
$ php artisan make:model Models/Users
app/Http/Models/User.php
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Support\Carbon;
class User extends Authenticatable implements JWTSubject
{
use Notifiable;
protected $carbon;
protected $now;
protected $primaryKey = 'user_id';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'user_name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
public function __construct()
{
$this->carbon = $carbon ?? new Carbon();
$this->now = $this->carbon->now()->timestamp;
}
public function getUserId()
{
return $this->user_id;
}
public function getUserName()
{
return $this->user_name;
}
public function getUserEmail()
{
return $this->email;
}
public function getJWTIdentifier()
{
return $this->getKey();
}
public function getJWTCustomClaims()
{
return [];
}
}
getJWTIdentifier(), getJWTCustomClaims()は定義する必要がある。
composer.json
"autoload": {
"psr-4": {
"App\\": "app/",
+ "Models\\": "app/Models/"
},
dump-autoloadで読ませる
$ composer dump-autoload
リクエストの作成
LoginRequest リクエストの作成
$ php artisan make:request LoginRequest
Requests/LoginRequest.php
<?php
namespace App\Http\Requests\Api;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
class LoginRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'email' => 'required|string|email',
'password' => 'required|string|min:6|max:10'
];
}
protected function failedValidation(Validator $validator) {
$res = response()->json([
'status' => 400,
'errors' => $validator->errors(),
], 400);
throw new HttpResponseException($res);
}
}
RegistUserRequest リクエストの作成
$ php artisan make:request RegistUserRequest
Requests/RegistUserRequest.php
<?php
namespace App\Http\Requests\Api;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
class RegistUserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'user_name' => 'required|string',
'email' => 'required|string|email|unique:users',
'password' => 'required|string|min:6|max:10'
];
}
protected function failedValidation(Validator $validator) {
$res = response()->json([
'status' => 400,
'errors' => $validator->errors(),
], 400);
throw new HttpResponseException($res);
}
}
config設定
トークンの時間設定
config/token.php
<?php
/**
* トークン用の設定
*/
return [
//有効期限の設定
'expire' => [
// デフォルト15分
'accessToken' => env('ACCESS_TOKEN_EXPIRATION_SECONDS', 900),
//デフォルト4週間
'refreshToken' => env('REFRESH_TOKEN_EXPIRATION_SECONDS', 2419200),
]
];
configを触ったのでキャッシュをクリアする
$ php artisan config:cache
jwt.phpの設定
config/jwt.php
ポイント① ブラックリストをfalseにする
/*
|--------------------------------------------------------------------------
| Blacklist Enabled
|--------------------------------------------------------------------------
|
| In order to invalidate tokens, you must have the blacklist enabled.
| If you do not want or need this functionality, then set this to false.
|
*/
- 'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true),
+ 'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', false),
- これをfalseにしないとloginリダイレクトが発動して実装しなくてはいけない
- またブラックリストを利用しない
これは私の事情で判断。
ポイント② HS256であることを確認
'algo' => env('JWT_ALGO', 'HS256'),
これは必須です。
コントローラの作成
App/Http/Controllers/Controller.php
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\ApiTokenCreateService;
use Tymon\JWTAuth\Facades\JWTAuth;
use App\Models\User;
use App\Http\Requests\Api\LoginRequest;
class LoginController extends Controller
{
/**
* @param App\Http\Requests\Api\LoginRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function login(LoginRequest $request)
{
$input = $request->only('email', 'password');
$token = null;
if (!$token = JWTAuth::attempt($input)) {
return response()->json([
'success' => false,
'message' => 'Invalid Email or Password',
], 401);
}
$user = User::where('email', $request->email)->first();
$ApiTokenCreateService = new ApiTokenCreateService($user);
return $ApiTokenCreateService->respondWithToken();
}
}
App/Http/Controllers/Api/V1_0/RegisterController.php
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\RegistUserRequest;
use App\Models\User;
use App\Services\ApiTokenCreateService;
class RegisterController extends Controller
{
public function register(RegistUserRequest $request)
{
$user = new User();
$user->user_name = $request->user_name;
$user->email = $request->email;
$user->password = bcrypt($request->password);
$user->save();
$userId = $user->user_id;
$user = User::find($userId);
$ApiTokenCreateService = new ApiTokenCreateService($user);
return $ApiTokenCreateService->respondWithToken();
}
}
App/Http/Controllers/Api/V1_0/RefreshTokenController.php
<?php
namespace App\Http\Controllers\Api\V1_0;
use App\Http\Controllers\Controller;
use App\Services\ApiTokenCreateService;
use Illuminate\Http\JsonResponse;
use App\Models\User;
class RefreshTokenController extends Controller
{
private $user;
private $user_id;
private $access_token;
public function __construct()
{
$this->middleware('auth:api', ['except' => ['login']]);
}
/**
* アクセストークンのリフレッシュ
*
* @Header - Authorization: Bearer <リフレッシュトークン>
* @Header - Content-Type: application/json
* @return \Illuminate\Http\JsonResponse
*/
public function refreshToken(): JsonResponse
{
$this->access_token = auth()->refresh();
$this->user_id = (auth()->user()->user_id);
$this->user = User::where('user_id', $this->user_id)->first();
return $this->respondWithToken();
}
/**
* トークンとユーザ情報のJSONデータを返却
*
* @return \Illuminate\Http\JsonResponse
*/
public function respondWithToken(): JsonResponse
{
return response()->json([
'token' => [
'access_token' => $this->access_token,
],
'profile' => [
'id' => $this->user->user_id,
'name' => $this->user->user_name,
'email' => $this->user->email
]
]);
}
}
Serviceの作成
App/Services/Service.php
<?php
namespace App\Services;
abstract class Service
{
}
App/Services/ApiTokenCreateService.php
<?php
namespace App\Services;
use App\Models\User;
use Carbon\Carbon;
use Tymon\JWTAuth\Facades\JWTAuth;
use Tymon\JWTAuth\Facades\JWTFactory;
class ApiTokenCreateService extends Service
{
protected $user;
protected $carbon;
protected $now;
public function __construct(User $user, Carbon $carbon = null)
{
$this->user = $user;
$this->carbon = $carbon ?? new Carbon();
$this->now = $this->carbon->now()->timestamp;
}
/**
* tokenとユーザ情報のJSONデータを返却
*
* @return \Illuminate\Http\JsonResponse
*/
public function respondWithToken() :object
{
return response()->json([
'token' => [
'access_token' => $this->createAccessToken(),
'refresh_token' => $this->createRefreshToken()
],
'profile' => [
'id' => $this->user->getUserId(),
'name' => $this->user->getUserName(),
'email' => $this->user->getUserEmail()
]
]);
}
/**
* API用のアクセストークンを作成
*
* @return string
*/
public function createAccessToken() :string
{
$customClaims = $this->getJWTCustomClaimsForAccessToken();
$payload = JWTFactory::make($customClaims);
$token = JWTAuth::encode($payload)->get();
return $token;
}
/**
* API用のリフレッシュトークンを作成
*
* @return string
*/
public function createRefreshToken() :string
{
$customClaims = $this->getJWTCustomClaimsForRefreshToken();
$payload = JWTFactory::make($customClaims);
$token = JWTAuth::encode($payload)->get();
return $token;
}
/**
* アクセストークン用CustomClaimsを返却
*
* @return object
*/
public function getJWTCustomClaimsForAccessToken() :object
{
$data = [
'sub' => $this->user->getUserId(),
'iat' => $this->now,
'exp' => $this->now + config('token.expire.accessToken')
];
return JWTFactory::customClaims($data);
}
/**
* リフレッシュトークン用CustomClaimsを返却
*
* @return object
*/
public function getJWTCustomClaimsForRefreshToken() :object
{
$data = [
'sub' => $this->user->getUserId(),
'iat' => $this->now,
'exp' => $this->now + config('token.expire.refreshToken')
];
return JWTFactory::customClaims($data);
}
}
ルーティング
routes/api.php
<?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::prefix('V1_0')->group(function () {
Route::middleware([
'jwt_auth', // JWTトークンによる認証を強制 - \Tymon\JWTAuth\Http\Middleware\Authenticate::class
])->group(function(){
Route::post('/refresh-token', 'Api\V1_0\RefreshTokenController@refreshToken');
});
Route::group([
"middleware" => 'guest:api', // 認証不要なAPIとして設定
], function () {
Route::post('/users/me', 'Api\V1_0\RegisterController@register');
Route::post('/login', 'Api\V1_0\LoginController@login');
Route::post('/login/guest', 'Api\V1_0\GuestLoginController@login');
});
});
アクセス
- ユーザ登録
・URL: http://localhost/api/V1_0/register
・メソッド:POST
・JSON{ "user_name": "yuu", "email": yuu@example.net "password": "P@s2w0rd" } - ログイン
・URL: http://localhost/api/V1_0/login
・メソッド:POST
・JSON{ "email": yuu@example.net "password": "P@s2w0rd" } - リフレッシュトークン
・URL: http://localhost/api/V1_0/login
・メソッド:GET
・Header

// Mac Postmanを使用
- Authorization: Bearer <リフレッシュトークン>
- Content-Type: application/json
こういう風にヘッダーにリフレッシュトークンを指定して、GETすると新たなアクセストークンが得られます。
トークンの内容確認

アクセストークンやリフレッシュトークンを貼り付けて確認しよう。
@see

