【SPA】Laravel + Vue.js + JWT認証でメールアドレス送信を行い登録するまで part.1 - inokawablog

    【SPA】Laravel + Vue.js + JWT認証でメールアドレス送信を行い登録するまで part.1

    SPAを作成する場合のLaravel + vue.js + JWTを使い、emailを送信し、仮登録を行い、本登録をする流れを作成します。

    これらを使って認証を行なっているものが少なかったのでまとめておきます。

     

    環境は以下の通りです。

    Node npm Vue.js Vue Router Vuex PHP Laravel
    12.4.0 6.9.0 2.5.17 3.1.2 3.1.1 7.1.16 5.8.34

     

    長かったので2パートに分けてあります。前半はjwtで認証を行うまでの設定を行い、後半でemailを使った仮登録から本登録までの流れを実装します。

    laravelに慣れていない場合は、LaravelのドキュメントまたはLaravelに関する他の記事を参照して、あらかじめ理解を深めてください。

     

    メールの認証にはメールテストツールのMailHogを使用します。

    今回はlaravelで用意されているmake:authは使用しません。

     

    早速やっていきましょう。

     

    JWT(JSON Web Token)とは

    わかる方は飛ばしてください。

     

    属性情報 (Claim) をJSONデータ構造で表現したトークンの仕様のことで、認証なんかに使われます。

    jwtは以下のもので構成されており、これら3つのパーツが . で区切られた文字列のことを指します

    1. ヘッダー(Base64文字列)
    2. クレーム情報、つまりデータ本体(Base64文字列)
    3. 署名(バイナリ文字列)

    ヘッダー

    メタ情報を含めます。alg には署名に使われているアルゴリズムを指定します。これは必須です。

    typ はトークンタイプを表します。”JWT”が推奨されますが、これは任意です。

     

    クレームデータ

    JSONをBase64urlエンコードした文字列

    クレーム 説明
    iss JWT発行者
    sub JWT発行者から払い出されたユーザ識別子。文字列またはURIで表わされる。⇒社員番号を設定
    aud JWTを利用することが想定された主体の識別子一覧
    exp JWTの有効期限を示す。⇒有効期限を設定
    nbf JWTが有効になる日時を示す
    iat JWTを発行した時刻を示す
    jti JWTのための一意な識別子。JWTのリプレイを防ぐことに利用する
    typ typヘッダパラメータと同じ値空間および同じ規則が適用される

     

    署名

    主に署名時に使用され、データが改ざんされているかどうかをチェックするために使われます。

    今回は、jwt-authを利用してjwtの認証を行います。

     

     

    Laravel アプリケーションのセットアップ

    ここは好きな名前に変えてください。今回は、laravel_jwt_vuejs_emailという名前でプロジェクトを作成します。

    composer create-project --prefer-dist laravel/laravel laravel_jwt_vuejs_email

     

    cd laravel_jwt_vuejs_email

    JWT認証パッケージを追加

    composer require tymon/jwt-auth:dev-develop

    「dev-develop」はまだ開発中であることを意味しています。

     

    JWT設定の公開

    artisan コマンドを使用してJWTパッケージ構成を公開します。

    php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

     

    config/app.phpに以下を追加してください。

    app.php

    'providers' => [
    		Tymon\JWTAuth\Providers\LaravelServiceProvider::class,
    ]
    'aliases' => [
    		'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,
    ],

     

     

    JWTシークレットを作成する

    秘密鍵を生成します。

    php artisan jwt:secret

     

    認証設定の更新

    config/auth.phpのデフォルトのガードとAPIドライバーを以下のように更新します

     

    'defaults' => [
        'guard' => 'api',
        'passwords' => 'users',
    ],
    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
    
        'api' => [
            'driver' => 'jwt',
            'provider' => 'users',
        ],
    ],
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\User::class,
        ],
    ],

     

    ユーザーモデルの更新

    app/User.phpを以下のように変更します。

    <?php
    namespace App;
    use Illuminate\Notifications\Notifiable;
    use Illuminate\Contracts\Auth\MustVerifyEmail;
    use Illuminate\Foundation\Auth\User as Authenticatable;
    use Tymon\JWTAuth\Contracts\JWTSubject;
    class User extends Authenticatable implements JWTSubject
    {
        use Notifiable;
    
        protected $fillable = [
            'name', 'email', 'password', 'role'
        ];
    
        protected $hidden = [
            'password', 'remember_token',
        ];
    
        public function getJWTIdentifier()
        {
            return $this->getKey();
        }
        
        public function getJWTCustomClaims()
        {
            return [];
        }
    }

    getJWTIdentifierはjwtのトークンを取得するための関数です。

    getJWTCustomClaimsは、前述したjwtのクレームデータの情報以外に追加的にjwtトークンに情報を追加したい時、この関数に戻り値(Return value)を修正します。

     

    認証ミドルウェアの更新

    app/Http/Middleware/Authenticate.php を更新します。handle と authenticate をオーバーライドし、json形式で返しています。この設定ではJSON形式の応答が必要なログインページのVue Jsで使用されるのでjson形式で返しています。

    Authenticate.php

    <?php
    namespace App\Http\Middleware;
    use Closure;
    use Illuminate\Auth\Middleware\Authenticate as Middleware;
    class Authenticate extends Middleware
    {
        // Override handle method
        public function handle($request, Closure $next, ...$guards)
        {
            if ($this->authenticate($request, $guards) === 'authentication_failed') {
                return response()->json(['error'=>'Unauthorized'],400);
            }
            return $next($request);
        }
        // Override authentication method
        protected function authenticate($request, array $guards)
        {
            if (empty($guards)) {
                $guards = [null];
            }
            foreach ($guards as $guard) {
                if ($this->auth->guard($guard)->check()) {
                    return $this->auth->shouldUse($guard);
                }
            }
            return 'authentication_failed';
        }
    }

     

    ルートの設定

    routes/api.php に以下ルートを追加してください。

    api.php

    Route::prefix('auth')->group(function () {
      Route::post('register', 'AuthController@register');
      Route::post('login', 'AuthController@login');
      // Refresh the JWT Token
      Route::get('refresh', 'AuthController@refresh');
      Route::middleware('auth:api')->group(function () {
          // Get user info
          Route::get('user', 'AuthController@user');
          // Logout user from application
          Route::post('logout', 'AuthController@logout');
      });
    });

     

    認証用コントローラーの作成

    php artisan make:controller AuthController

    app/Http/AuthController.php が作成されるので、以下のように変更してください。

    AuthController.php

    <?php
    namespace App\Http\Controllers;
    use Illuminate\Http\Request;
    use Auth;
    use JWTAuth;
    use Validator;
    use App\User;
    class AuthController extends Controller
    {
        /**
         * Register a new user
         */
        public function register(Request $request)
        {
            $v = Validator::make($request->all(), [
                'name' => 'required|min:3',
                'email' => 'required|email|unique:users',
                'password'  => 'required|min:3|confirmed',
            ]);
            if ($v->fails())
            {
                return response()->json([
                    'status' => 'error',
                    'errors' => $v->errors()
                ], 422);
            }
            $user = new User();
            $user->name = $request->name;
            $user->email = $request->email;
            $user->password = bcrypt($request->password);
            $user->save();
            return response()->json(['status' => 'success'], 200);
        }
        /**
         * Login user and return a token
         */
        public function login(Request $request)
        {
            $credentials = $request->only('email', 'password');
            if ($token = $this->guard()->attempt($credentials)) {
                return response()->json(['status' => 'success'], 200)->header('Authorization', $token);
            }
            return response()->json(['error' => 'login_error'], 401);
        }
        /**
         * Logout User
         */
        public function logout()
        {
            $this->guard()->logout();
            return response()->json([
                'status' => 'success',
                'msg' => 'Logged out Successfully.'
            ], 200);
        }
        /**
         * Get authenticated user
         */
        public function user(Request $request)
        {
            $user = JWTAuth::parseToken()->authenticate();
            return response()->json([
                'status' => 'success',
                'data' => $user
            ]);
        }
        /**
         * Refresh JWT token
         */
        public function refresh()
        {
            if ($token = JWTAuth::getToken()) {
                return response()
                    ->json(['status' => 'successs'], 200)
                    ->header('Authorization', $token);
            }
            return response()->json(['error' => 'refresh_token_error'], 401);
        }
        /**
         * Return auth guard
         */
        private function guard()
        {
            return Auth::guard();
        }
    }

     

    register()

    バリデーションを行い、検証後に新しいユーザーを作成します。

    login()

    Auth :: guard()を使用しています。 attempt() メソッドは指定された資格情報をチェックし、成功後、応答のヘッダーで返されるトークンを生成します。

    refresh()

    現在のトークンの有効期限が切れている場合、refresh() はトークンを再生成します。config / jwt.php でトークンの期間を指定できます。

    jwt.php

    'ttl' => env('JWT_TTL', 60),

    このように jwt.php で指定されており、JWT_TTLは、.env ファイルのJWT_TTLで設定します。

    .env

    JWT_TTL=10

    デフォルトでは、JWTトークンは60分間(1時間)有効です。

    logout()

    トークンをリセットします。

    user()

    ユーザー情報を取得します。

     

    ユーザーの作成

    php artisan migrate

     

    Vuexのインストール

    npm install vuex --save

     

    Vue Routerのインストール

    npm install vue-router

     

    依存関係をインストールしてください。

    npm install

     

     

    ここまででpackage.jsonは以下のようになっています。

    package.json

    {
        "private": true,
        "scripts": {
            "dev": "npm run development",
            "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
            "watch": "npm run development -- --watch",
            "watch-poll": "npm run watch -- --watch-poll",
            "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
            "prod": "npm run production",
            "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
        },
        "devDependencies": {
            "axios": "^0.18.1",
            "bootstrap": "^4.1.0",
            "browser-sync": "^2.26.3",
            "browser-sync-webpack-plugin": "2.0.1",
            "cross-env": "^5.1",
            "laravel-mix": "^4.0.7",
            "popper.js": "^1.12",
            "resolve-url-loader": "^2.3.1",
            "sass": "^1.15.2",
            "sass-loader": "^7.1.0",
            "vue": "^2.5.17",
            "vue-template-compiler": "^2.6.10"
        },
        "dependencies": {
            "vue-router": "^3.1.3",
            "vuex": "^3.1.1"
        }
    }

     


     

    次回は、vueファイルを作成し、emailで仮登録から本登録を行うまでをやっていきます。