1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Laravel7 + JWT-Authでログイン認証機能を実装

Last updated at Posted at 2021-03-13

まえおき

認証って色々種類があって何を使うべきなのか迷いますよね...
・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モデル編集

App/User.php
<?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がインターフェースなので実装する必要がある)

認証ガード変更

config/auth.php
<?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定義

routes/api.php
<?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
AuthController.php
<?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
MypageController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class MypageController extends Controller
{
    public function index()
    {
        return response()->json(auth()->user());
    }
}

認証に成功したユーザー(ログイン中)の情報をただ返しているだけです...

POSTMANでAPI検証

新規登録

スクリーンショット 2021-03-12 2.04.25.png

ログイン

スクリーンショット 2021-03-12 2.08.38.png

マイページ(未認証時)

スクリーンショット 2021-03-12 2.10.36.png
ちゃんと401が返ってきています
※HeadersにAccept application/jsonを追加しないとloginルートにリダイレクトしようとします

マイページ(認証済 トークン付与)

スクリーンショット 2021-03-12 2.13.51.png

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)

app/providers/RouteServiceProvider.php
    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エラーが発生してしまうので
下記の様に設定すると発生しなくなる

.env.development
NODE_ENV='development'
VUE_APP_API_BASE_URL='http://127.0.0.1:8000/api'
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?