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

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

    前回はjwt認証を行うまでをやりました。今回はemailで仮登録を行い、本登録をしてログインするまでを行います。

     

    mailhog

    電子メールテストツールであるmailhogをインストールします。

    brew install mailhog

     

    mailhogを立ち上げておきます。

    brew services start mailhog

     

    Laravelの.envファイルのメールの部分を以下のように変更します。

    MAIL_DRIVER=smtp
    MAIL_HOST=localhost
    MAIL_PORT=1025
    MAIL_USERNAME=null
    MAIL_PASSWORD=null
    MAIL_ENCRYPTION=null
    MAIL_FROM_ADDRESS=from@example.com
    MAIL_FROM_NAME=AppName

     

    以下のコマンドを打ちましょう

    php artisan config:cache

    .envファイルの設定を反映させます。

     

    そして http://localhost:8025/ にアクセスし、mailhogのページが表示されるか確認してください。

    以下のようなページが表示されれば成功です。

    MailHog

     

    Usersテーブルにtokenを追加する

    仮会員登録で発行するURLtokenのフィールドを、usersテーブルに追加します。

    php artisan make:migration add_columns_users_table

     

    class AddColumnsUsersTable extends Migration
    {
        public function up()
        {
            Schema::table('users', function (Blueprint $table) {
                $table->tinyInteger('email_verified')->default(0);
                $table->string('email_verify_token')->nullable();
            });
        }
    
        public function down()
        {
            Schema::table('users', function (Blueprint $table) {
                $table->dropColumn('email_verified');
                $table->dropColumn('email_verify_token');
            });
        }
    }

    email_verified:認証済みかどうかの判定

    email_verify_token:email用トークン

     

    変更を反映します。

    php artisan migrate

     

    次に、認証メールに使用するviewと、tokenを返すクラスを作成します。

    php artisan make:mail EmailVerification

    app/Mail 以下にEmailVerification.phpが作成されます。

    このクラスにメール送信に使用するviewやメールタイトルなどの設定を記述することになります。

    内容を以下のように変更してください。

    EmailVerification.php

    <?php
    
    namespace App\Mail;
    
    use Illuminate\Bus\Queueable;
    use Illuminate\Mail\Mailable;
    use Illuminate\Queue\SerializesModels;
    use Illuminate\Contracts\Queue\ShouldQueue;
    
    class EmailVerification extends Mailable
    {
        use Queueable, SerializesModels;
    
        protected $user;
    
        public function __construct($user)
        {
          $this->user = $user;
        }
    
        public function build()
        {
          return $this
          ->subject('仮登録が完了しました')
          ->view('auth.email.pre_register')
          ->with(['token' => $this->user->email_verify_token,]);
        }
    }

     

    Email用テンプレートの作成

    Emailの本文となるviewを作成します。

    resources/views/auth/email にpre_register.blade.phpを以下の内容で作成してください。

    pre_register.blade.php

    サイトへのアカウント仮登録が完了しました。<br>
    <br>
    以下のURLからログインして、本登録を完了させてください。<br>
    <a href="{{url('register/verify/'.$token)}}">{{url('register/verify/'.$token)}}</a><br>

     

    このリンクをクリックすると本登録されるようになります。

     

    Userモデルの更新

    Userモデルの$fillableを以下のようemail_verified_atとemail_verify_tokenを追加してください。

    protected $fillable = [
          'name',
          'email',
          'email_verified_at',
          'email_verify_token',
          'password',
     ];

     

    メール認証用ページの作成

    仮登録した後メッセージを表示するためのコンポーネントを作成します。

    resources/js/pages/auth/RegisterEmailMsg.vueを以下の内容で作成してください。

    RegisterEmailMsg.vue

    <template>
      <div class="container">
        <div class="row justify-content-center">
          <div class="col-md-8">
            <div class="card">
              <div class="card-body">
                <p>{{msg}}</p>
              </div>
            </div>
          </div>
        </div>
      </div>
    </template>
    <script>
      export default {
        data() {
          return {
            msg: '',
          }
        },
        created(){
          if (this.$route.params.status === 'success') {
            this.msg = '本登録が完了しました。ログイン画面からログインを行なってください。'
          }else if (this.$route.params.status === 'fail') {
            this.msg = 'メール認証に失敗しました。再度、メールからリンクをクリックしてください。'
          }else if (this.$route.params.status === 'pre_register') {
            this.msg = '仮登録が完了しました。メールのリンクをクリックして本登録をしてください。'
          }else if (this.$route.params.status === 'invalid') {
            this.msg = '無効なトークンです。'
          }else if (this.$route.params.status === 'exist') {
            this.msg = 'すでに本登録されています。ログインして利用してください。'
          }else{
            this.$router.push({name: 'not_found'})
          }
        },
        methods: {
        }
      }
    </script>

    Vue Routerのurlでparamを受け取ってそれに対してメッセージを表示します。

    今はこの方法しか思いつかなかったのでもっといい方法があるのかもしれません。

     

    AuthController.phpの更新

    AuthController.phpをメール認証用に更新します。

    以下のように内容を変更してください。

    AuthController.php

    <?php
    namespace App\Http\Controllers;
    use Illuminate\Http\Request;
    // use Auth;
    use Illuminate\Support\Facades\Auth;
    use Validator;
    use JWTAuth;
    use App\User;
    use Illuminate\Support\Facades\Log;
    use Carbon\Carbon;
    use Illuminate\Support\Facades\Mail;
    use App\Mail\EmailVerification;
    
    class AuthController extends Controller
    {
        public function checkRegisterEmail($email_token)
        {
          // 使用可能なトークンか
          if ( !User::where('email_verify_token',$email_token)->exists() ){
              redirect('/register/email/invalid');
          } else {
              $user = User::where('email_verify_token', $email_token)->first();
              // 本登録済みユーザーか
              if ($user->email_verified == 1){
                  // logger("status". $user->email_verified );
                  return redirect('/register/email/exist');
              }
              // ユーザーステータス更新
              $user->email_verified = 1;
              $user->email_verified_at = Carbon::now();
              if($user->save()) {
                  return redirect('/register/email/success');
              } else{
                  return redirect('/register/email/fail');
              }
          }
        }
        /**
         * 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->email_verify_token = base64_encode($request->email);
            $user->save();
            Mail::to($request->email)->send(new EmailVerification($user));
            return response()->json(['status' => 'success'], 200);
        }
        /**
         * Login user and return a token
         */
        public function login(Request $request)
        {
          if ($request->email) {
            $user = User::where('email', $request->email)->first();
            if (!empty($user['email_verified_at'])) {
              $credentials = $request->only('email', 'password');
              if ($token = $this->guard()->attempt($credentials)) {
                  return response()->json(['status' => 'success'], 200)->header('Authorization', $token);
              }
              return response()->json(['error' => 'The information entered is incorrect.'], 401);
            }else{
              return response()->json(['error' => 'This registration has not been completed. Please click the link of the sent email.'], 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()) {
            // if ($token = $this->guard()->refresh()) {
                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();
        }
    }

     

    変更点を確認していきます。

    checkRegisterEmail($email_token)

    $user = User::where('email_verify_token', $email_token)->first();
              // 本登録済みユーザーか
              if ($user->email_verified == 1){
                  // logger("status". $user->email_verified );
                  return redirect('/register/email/exist');
              }

    本登録されているされている場合は/register/email/exitにリダイレクトします。

    仮登録の場合は、email_verifiedを1にして、email_verified_atに現在時刻を入れ、ステータスを更新します。

    // ユーザーステータス更新
    $user->email_verified = 1;
    $user->email_verified_at = Carbon::now();
    if($user->save()) {
        return redirect('/register/email/success');
    } else{
        return redirect('/register/email/fail');
    }

     

    register()

    $user->email_verify_token = base64_encode($request->email);
    Mail::to($request->email)->send(new EmailVerification($user));

    ユーザーを仮登録するときに、emailをbase64でエンコードしてemail_verify_tokenとして登録し、メールを送信しています。

     

    ルートの更新

    resources/routes/web.phpに以下をanyより前に追加してください。

    Route::get('/register/verify/{token}', 'AuthController@checkRegisterEmail')->name('equation');

    メールのリンクをクリックした後に、urlからトークンを取得し、データベースと照合して本登録を行います。

     

    さらに、resources/js/router/index.js に以下を追加してください。仮登録後に表示するページのルートを定義します。

    import RegisterEmailMsg      from '../pages/auth/RegisterEmailMsg.vue'
      routes: [
    // email verify
        { path: '/register/email/:status',   name: 'register_email_msg',     component: RegisterEmailMsg,      meta: { auth: false        }},
    ],

     

    最後に/registerから登録された後にリダイレクトする設定を更新して終了です。

    /resources/auth.jsのルートを変更します。

    -  registerData:   { url: 'api/auth/register',   method: 'POST',   redirect: '/login'      },
      +  registerData:   { url: 'api/auth/register',   method: 'POST',   redirect: '/register/email/pre_register'      },

     

    登録後、/register/email/pre_registerにアクセスし、仮登録のメッセージを表示させます。

    これで以上です。実際に/registerから登録を行い、メールが送信されるかを確認します。

     

    一回、仮登録の状態でログインしようとすると、ログインできないと思います。

    そして、そのメールのリンクを踏んで、本登録をしてみてください。

     

    このサンプルはgithubにおいてあります。

     


    お疲れ様です。以上で終わりです。

    ここまでのソースコードはgithubに置いてあります。