もくじ
備考
- 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