モダンな技術を全く知らないSIer5年目題Webサービスを作ることになったため0から勉強する〜Nuxt +メール認証+パスワードリセット〜

# はじめに
現在SIer5年目でjavascript(Jqueryのみ)、PHP(フレームワーク無し)を2年ほど、C#(Windowsアプリ)3年ほどやってきました。
色々なご縁があり、個人で最近Webサービスの立ち上げをやることになったのですが何せ本当にWebサービスを立ち上げるための知識がほぼ0に等しいです:sob:

ただ今後のキャリアを考えた時に今のままではいけないと思いチャレンジすることにしました。

まずは最初に技術を習得しないといけないので、学ぶ＆アウトプットするために毎回投稿していこうと思います。
今後身についていこうと思ってるのは下記のような技術です。
**AWS**
**Docker**
**CI/CD環境の構築**
**Laravel**
**Nuxt.js**
今回はLaravel+Nuxtについて学んでいきます。

# 今回学ぶこと
Laravel＋Nuxtでのメール認証機能とパスワードリセット機能を作成していこうかと思います。
今回はLaravel API側の実装をしていきます

# 参考サイト
[jwt-auth公式サイト](https://jwt-auth.readthedocs.io/en/develop/)

[こちらのサイトを参考にさせていただきました](https://qiita.com/ayasamind/items/abadb9737e9d6480806b)

[メール認証の概要](https://qiita.com/yamaji_daisuke/items/731868a4de6037794976)は個々がわかりやっすかったです。

# 前提
Laravel 5.8
Nuxt　2.5.4 
jwt-auth

[メール認証とパスワードリセットのAPI側実装](https://qiita.com/gemetasu1103/items/0a1f8e1fca8594197845)はこちらで実装してるのでこちらを参照ください

[最終的なソースコードはこちらになります。](https://github.com/hajime1103/nuxt-laravel-jwt-auth-example)
※今回は、Nuxt側の実装でLaravel側も変更している箇所もあるのでそちらもソースコード確認してもらえたらと思います。

# Nuxt側のvueファイルを作成する
フロント側はメール認証＋パスワード再発行を実現するための画面とミドルウェアを作成します。

| vue_file       | 概要       |
|:-----------------|:------------------|
| client/pages/auth/resetpassword.vue | パスワードを変更する画面、パスワード変更時にトークンも一緒に送る |
| client/pages/auth/resendverify.vue | メール認証のリンクを送るための画面 |
| client/pages/auth/forgotpassword.vue | メールアドレスを入力し、パスワード再発行メールを送信する画面 |
| client/pages/auth/emailverification.vue | メール認証中の際に表示する画面 |
| client/middleware/emailVerify.js | メール認証済みかチェックを行うミドルウェア |

# パスワードリセットを実装する
まずはパスワードリセットを実装するにあたっての処理の流れはこんな感じになります。

1. ログイン画面から`Forgot Your Password `リンクをクリックし、パスワード再発行のメールアドレスを入力する`forgotpassword.vue`を表示する
2. メールアドレスを入力し、再発行ボタンを押下すると`localhost/api/auth/password/email`APIに対してリクエストが投げられ指定したアドレスにパスワード再発行メールを送る
3. メールのリンクをクリックすると`resetpassword.vue`を表示し、リセットするパスワードを入力し、リンクに乗ってるqueryURLに対してリクエストを投げる
4. パスワードリセットが完了したら、ログイン画面に遷移する

### ログイン画面から`forgotpassword.vue`に遷移するリンクを作成

```client/pages/auth/login.vue
<div class="form-group">
  <input type="submit" value="Login" class="btn btn-default w-100">
  <nuxt-link class="nav-link" to="/auth/forgotpassword">Forgot Your Password</nuxt-link>
</div>
```

### パスワードリセット対象のメールアドレス入力画面を実装する

```client/pages/auth/forgotpassword.vue
<template>
  <div class="container">
    <div class="col-md-6 offset-md-3">
      <div class="card mt-4">
        <div class="card-header">
          <p class="mb-0">ForgotPassword</p>
        </div>
        <div class="card-body">
          <b-alert variant="success"　v-model="showSuccessAlert">I have sent a password reissue email</b-alert>　<!-- パスワードリセットメールを送れたことをメッセージで表示する -->
          <form @submit.prevent="ReSendVerifyEmail">
            <div class="form-group">
              <label>Email</label>
              <input v-model="form.email" type="email" class="form-control" :class="{ 'is-invalid': errors.email }" placeholder="Email">
            </div>
            <div class="form-group">
              <input type="submit" value="SendResetLinkEmail" class="btn btn-default w-100">
            </div>
          </form>
        </div>
      </div>
    </div>

  </div>
</template>

<script>
  export default {
    middleware: 'guest', //ログインであればリダイレクトする
    data() {
      return {
        form: {
          email: '',
        },
        showSuccessAlert: false

      }
    },
    methods: {
      async ReSendVerifyEmail(){
        // パスワードリセットのメール送信APIを実行する
        await this.$axios.post('/auth/password/email', this.form)
          .then(data => {
            // 送信完了メッセージ表示
            this.showSuccessAlert = true;

          })
          .catch(err=> {

            console.log(err);
          });


      }
    }

  }
</script>

```

### リセットするパスワード入力する画面

```client/pages/auth/resetpassword.vue
<template>
  <div class="container">
    <div class="col-md-6 offset-md-3">
      <div class="card mt-4">
        <div class="card-header">
          <p class="mb-0">Register</p>
        </div>
        <div class="card-body">
          <form @submit.prevent="ResetPassword"> <!-- 標準のsubmitは実行しない -->
            <div class="form-group">
              <label>Email</label>
              <input v-model="form.email" type="email" class="form-control" :class="{ 'is-invalid': errors.email }" placeholder="Email">
              <div class="invalid-feedback" v-if="errors.email">
                {{ errors.email[0] }}
              </div>
            </div>
            <div class="form-group">
              <label>Password</label>
              <input v-model="form.password" type="password" class="form-control" :class="{ 'is-invalid': errors.password }" placeholder="Password">
              <div class="invalid-feedback" v-if="errors.password">
                {{ errors.password[0] }}
              </div>
            </div>
            <div class="form-group">
              <label>Password Confirmation</label>
              <input v-model="form.password_confirmation" type="password" class="form-control" :class="{ 'is-invalid': errors.password }" placeholder="Password">
              <div class="invalid-feedback" v-if="errors.password">
                {{ errors.password[0] }}
              </div>
            </div>
            <div class="form-group">
              <input type="submit" value="Register" class="btn btn-default w-100">
            </div>
          </form>
        </div>
      </div>
    </div>

  </div>
</template>

<script>
  export default {
    middleware: 'guest', //ログイン状態であればリダイレクトする
    data() {
      return {
        form: {
          email: '',                  // リセット対象のメールアドレス
          password: '',               // 新しいパスワード
          password_confirmation: '',  // 新しいパスワード確認
          token: ''                   // パスワードリセット実行するための一時的なトークン
        },
        requestUrl: ''
      }
    },

    created() {
      this.setQuery()
    },

    methods: {
      async ResetPassword(){ // パスワードリセットリクエストを投げる関数
        await this.$axios.$post(this.requestUrl, this.form)
          .then(data => {
            this.$router.push('/auth/login');
          })
          .catch(err=> {
            console.log(err);
          });
      },
      setQuery() { // getリクエストのパラメータを取得する関数
        this.requestUrl = this.$route.query.queryURL || ''; // パスワードリセットAPIのURL
        this.form.token = this.$route.query.token || '';    // パスワードリセットするために必要なToken
      },
    }

  }
</script>

```

# メール認証を実装する
メール認証を実装するにあたっての処理流れはこんな感じになります

1. ユーザ登録後にメール認証するためのメールを送信する
2. メールからメール認証画面に遷移する
3. メール認証画面では、queryURLにそのままリクエストを投げてメール認証を行い、成功したら画面遷移を行う

```client/pages/auth/emailverification.vue
<template>
  <div class="container">
    <div class="col-md-6 offset-md-3">
      <div class="card mt-4">
        <div class="card-header">
          <p class="mb-0">During Verification Your Email Address</p>
        </div>
        <div class="card-body">
          <label>Validating your email address.</label>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
  export default {
    middleware: 'auth', //未ログイン状態であればリダイレクトする
    data() {
      return {
        queryURL: ''
      }
    },

    async mounted() {
      const queryURL = this.$route.query.queryURL || '';
      if (queryURL != '') {
        await this.$axios.$get(queryURL)
          .then(data => {
            this.$auth.fetchUser();             // メール認証が完了したため、ユーザ情報を再取得する
            this.$router.push({name: 'index'});
          })
          .catch(err => {
            alert('メール認証が失敗しました。再度メール認証を行ってください。');
            this.$router.push('/auth/resendverify');
          });
      }
    },
  }
</script>

```

## メール認証チェックを行い、メール認証が必要な画面にアクセスした場合、認証メールを再送する画面にリダイレクトする

###  メール認証チェックを行うミドルウェアを実装する

`email_verified_at`はLaravelのUserモデルにはデフォルト項目であり
メール認証された場合はここに日時が入ってるため
`null`の場合は未認証と判断し、リダイレクトを行う

```client/middleware/emailVerify.js
//メールアドレスが認証されていなければ、メール認証の送信画面に遷移する
export default function({ store, redirect, app }) {
  if(app.$auth.user['email_verified_at'] == null) {
    return redirect('/auth/resendverify');
  }
}
```

### メール認証チェックを行う

ダッシュボードは未認証ユーザはアクセスできないようにする

```client/pages/dashboard.vue
<template>
  <div class="container">
    <h1>Welcome to the dashboard</h1>
  </div>
</template>

<script>
  export default {
    middleware: 'auth',
    middleware: 'emailVerify' // メール認証チェックを行う
  }
</script>
```

### 認証メールの再送信画面を実装する

```client/pages/auth/resendverify.vue
<template>
  <div class="container">
    <div class="col-md-6 offset-md-3">
      <div class="card mt-4">
        <div class="card-header">
          <p class="mb-0">Verify Your Email Address</p>
        </div>
        <div class="card-body">
          <b-alert variant="success" v-model="showSuccessAlert">I have sent a password reissue email</b-alert>
          <label>Email verification has not been completed yet.
            Please press the button below to complete e-mail authentication</label>
          <div>
            <b-button block variant="primary" @click="ReSendVerifyEmail">Verify Email</b-button>
          </div>
        </div>
      </div>
    </div>

  </div>
</template>

<script>
  export default {
    middleware: 'auth', //ログインしてなければリダイレクトする
    data() {
      return {
        form: {
          email: '',
        },
        showSuccessAlert: false,
      }
    },

    methods: {
      async ReSendVerifyEmail(){
        await this.$axios.post('/auth/email/resend', this.form)
          .then(data => {
            this.showSuccessAlert = true;
          })
          .catch(err=> {
            console.log(err);
          });
      },

    }

  }
</script>
```

# まとめ
フロント側はLaravelの`make:auth`で作成されるViewを参考に作りました
独学なので、おそらくもっと良い実装方法はあるかと思います。。。
もしこうした方がいいとかこのサイトは参考になるよなどあれば教えてもらえると幸いです。

あと、ログイン情報を保持するなどの機能はまだ実装してないのでそちらも実装できたらと思います。