PHP

jwt-auth + Laravel6 アクセストークンとリフレッシュトークン

 

備考

 

設定例

  • アクセストークン有効期限: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;
}

 

 

App/Http/Controllers/Api/V1_0/LoginController.php
<?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すると新たなアクセストークンが得られます。

トークンの内容確認

https://jwt.io/

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

 

 

@see

 

Amazonおすすめ

iPad 9世代 2021年最新作

iPad 9世代出たから買い替え。安いぞ!🐱 初めてならiPad。Kindleを外で見るならiPad mini。ほとんどの人には通常のiPadをおすすめします><

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)