【Nuxt.js・TailwindCSS】Firebase Authでバリデーションプラグインを使用せずに正規表現でセキュリティ対策（アカウント登録）

#はじめに
こんにちは。
こちらの記事では、Firebase Authで正規表現を用いたバリデーションを行う方法を記しています。
誤っている点がございましたらコメントいただけると幸いです。

##なぜ正規表現を使用するのか
Firebase Authでは、「パスワードが5文字以下のとき」や「すでに登録済みのメールアドレスを使おうとしたとき」など、決まった条件に対しては特定のエラーメッセージが返すことができます。

しかし、英字または数字のみでパスワードを登録することができてしまうため、`不正ログインされる原因`になりかねません。
そこで、英数字を含めないと登録できないように処理を行うため、正規表現を使用します。

##実装手順
今回はアカウント作成画面で、メールアドレスとパスワードで登録できる処理を記述します。(storeのvuexとpuluginsのfirebaseの記述は省略します)

###1. アカウント登録ページの作成
下記のようにシンプルなアカウント登録ページを作成します。
何が行われているか簡単に説明すると、
正常に登録できた場合は、アラートで通知してからstoreでログイン状態をログイン済みに設定、メインページへ遷移する。
エラーが発生した場合は、inputの下のpタグにエラーメッセージを表示させる。
FirebaseAuthが保持しているエラーメッセージは[こちら](https://firebase.google.com/docs/reference/js/v8/firebase.auth.Auth#index)から確認できる。

```html:pages/register.vue
<template>
  <div>
    <div class="mt-12 px-4 py-40 bg-gray-100 md:mt-20">
      <div class="container mx-auto py-8 bg-white md:w-160">
        <h2 class="text-center text-2xl font-bold">アカウント登録</h2>
        <form @submit.prevent class="px-20 md:px-40">
          <div>
            <div class="mt-12 w-full">
              <input
                type="email"
                name="email"
                required="required"
                placeholder="メールアドレス"
                v-model="email"
                class="pl-4 w-full h-14 text-base border-2 border-solid border-black border-opacity-25 hover:border-opacity-50 rounded-md transition duration-300"
                autofocus
              />
              <p class="text-red-400">{{ emailErrorMassage }}</p>
            </div>
            <div class="mt-12 w-full">
              <input
                type="password"
                name="password"
                required="required"
                placeholder="パスワード"
                v-model="password"
                class="pl-4 w-full h-14 text-base border-2 border-solid border-black border-opacity-25 hover:border-opacity-50 rounded-md transition duration-300"
              />
              <p class="text-red-400">
                {{ passwordErrorMassage }}
              </p>
            </div>
            <button
              type="submit"
              @click="register"
              class="block mt-8 mx-auto w-2/3 h-12 text-white text-xl bg-gray-200 hover:bg-red-500 rounded-md cursor-pointer transition duration-300"
            >
              送信
            </button>
          </div>
        </form>
      </div>
    </div>
  </div>
</template>
```

```javascript:pages/register.vue
<script>
export default {
  data: function() {
    return {
      email: "",
      password: "",
      emailErrorMassage: "",
      passwordErrorMassage: ""
    };
  },
  methods: {
    register() {
      firebase.auth()
        .createUserWithEmailAndPassword(this.email, this.password)
        .then(user => {
          alert("登録が完了しました。");
          this.$store.dispatch("checkLogin");
          this.$router.push("/main");
        })
        .catch(error => {
          console.log({ code: error.code, message: error.message });
          if (error.code === "auth/invalid-email") {
            this.emailErrorMassage = "このメールアドレスは無効です。";
          } else if (error.code === "auth/email-already-in-use") {
            this.emailErrorMassage =
              "このメールアドレスは既に使用されています。";
          } else {
            alert(
              "エラーにより登録ができませんでした。恐れ入りますが再度お試しください。"
            );
          }
        });
    }
  }
};
</script>
```

##2. 正規表現の追記
下記の通り、`register()`の下に正規表現でメールアドレスとパスワードを監視する処理を記述する。inputが空欄の時は入力を求めるメッセージを追記し、どちらかのエラーメッセ時が入っている状態であれば、firebaseを呼び出さずに処理を終了させる。

```javascript:pages/register.vue
<script>
export default {
  data: function() {
    return {
      email: "",
      password: "",
      emailErrorMassage: "",
      passwordErrorMassage: "",
      emailRegexp: /^[A-Za-z0-9]{1}[A-Za-z0-9_.-]*@{1}[A-Za-z0-9_.-]{1,}\.[A-Za-z0-9]{1,}$/,
      passwordRegexp: /^(?=.*?[a-z])(?=.*?\d)[a-z\d]{8,20}$/i
    };
  },
  methods: {
    register() {
      if (!this.emailRegexp.test(this.email)) {
        this.emailErrorMassage =
          "このメールアドレスは無効です。正しい形式で入力してください。";
      }
      if (!this.passwordRegexp.test(this.password)) {
        this.passwordErrorMassage =
          "このパスワードは無効です。半角英数字を含んで8-20文字の範囲内で入力してください。";
      }
      if (this.email === "") {
        this.emailErrorMassage = "メールアドレスを入力してください。";
      }
      if (this.password === "") {
        this.passwordErrorMassage = "パスワードを入力してください。";
      }
      if (this.emailErrorMassage !== "" || this.passwordErrorMassage !== "") {
        return;
      }
      firebase.auth()
        .createUserWithEmailAndPassword(this.email, this.password)
        .then(user => {
          alert("登録が完了しました。");
          this.$store.dispatch("checkLogin");
          this.$router.push("/main");
        })
        .catch(error => {
          console.log({ code: error.code, message: error.message });
          if (error.code === "auth/invalid-email") {
            this.emailErrorMassage = "このメールアドレスは無効です。";
          } else if (error.code === "auth/email-already-in-use") {
            this.emailErrorMassage =
              "このメールアドレスは既に使用されています。";
          } else {
            alert(
              "エラーにより登録ができませんでした。恐れ入りますが再度お試しください。"
            );
          }
        });
    }
  }
};
</script>
```

##3. 入力中はエラーメッセージを非表示にする
このままの実装でも問題はないが、エラーメッセージが表示された後に修正しようとすると、入力中も表示されたままの状態になってしまう。
ユーザを考慮すると、修正している最中もエラーメッセージが表示されていてはストレスをかけてしまうので、修正中は非表示にする処理を記述します。

```html:pages/register.vue
<template>
  <div>
    <div class="mt-12 px-4 py-40 bg-gray-100 md:mt-20">
      <div class="container mx-auto py-8 bg-white md:w-160">
        <h2 class="text-center text-2xl font-bold">アカウント登録</h2>
        <form @submit.prevent class="px-20 md:px-40">
          <div>
            <div class="mt-12 w-full">
              <input
                type="email"
                name="email"
                required="required"
                placeholder="メールアドレス"
                v-model="email"
                @input="isInput"
                class="pl-4 w-full h-14 text-base border-2 border-solid border-black border-opacity-25 hover:border-opacity-50 rounded-md transition duration-300"
                autofocus
              />
              <p class="text-red-400">{{ emailErrorMassage }}</p>
            </div>
            <div class="mt-12 w-full">
              <input
                type="password"
                name="password"
                required="required"
                placeholder="パスワード"
                v-model="password"
                @input="isInput"
                class="pl-4 w-full h-14 text-base border-2 border-solid border-black border-opacity-25 hover:border-opacity-50 rounded-md transition duration-300"
              />
              <p class="text-red-400">
                {{ passwordErrorMassage }}
              </p>
            </div>
            <button
              type="submit"
              @click="register"
              class="block mt-8 mx-auto w-2/3 h-12 text-white text-xl bg-gray-200 hover:bg-red-500 rounded-md cursor-pointer transition duration-300"
            >
              送信
            </button>
          </div>
        </form>
      </div>
    </div>
  </div>
</template>
```

```javascript:pages/register.vue
<script>
export default {
  data: function() {
    return {
      email: "",
      password: "",
      emailErrorMassage: "",
      passwordErrorMassage: "",
      emailRegexp: /^[A-Za-z0-9]{1}[A-Za-z0-9_.-]*@{1}[A-Za-z0-9_.-]{1,}\.[A-Za-z0-9]{1,}$/,
      passwordRegexp: /^(?=.*?[a-z])(?=.*?\d)[a-z\d]{8,20}$/i
    };
  },
  methods: {
    register() {
      if (!this.emailRegexp.test(this.email)) {
        this.emailErrorMassage =
          "このメールアドレスは無効です。正しい形式で入力してください。";
      }
      if (!this.passwordRegexp.test(this.password)) {
        this.passwordErrorMassage =
          "このパスワードは無効です。半角英数字を含んで8-20文字の範囲内で入力してください。";
      }
      if (this.email === "") {
        this.emailErrorMassage = "メールアドレスを入力してください。";
      }
      if (this.password === "") {
        this.passwordErrorMassage = "パスワードを入力してください。";
      }
      if (this.emailErrorMassage !== "" || this.passwordErrorMassage !== "") {
        return;
      }
      firebase.auth()
        .createUserWithEmailAndPassword(this.email, this.password)
        .then(user => {
          alert("登録が完了しました。");
          this.$store.dispatch("checkLogin");
          this.$router.push("/main");
        })
        .catch(error => {
          console.log({ code: error.code, message: error.message });
          if (error.code === "auth/invalid-email") {
            this.emailErrorMassage = "このメールアドレスは無効です。";
          } else if (error.code === "auth/email-already-in-use") {
            this.emailErrorMassage =
              "このメールアドレスは既に使用されています。";
          } else {
            alert(
              "エラーにより登録ができませんでした。恐れ入りますが再度お試しください。"
            );
          }
        });
    },
    isInput() {
      this.emailErrorMassage = "";
      this.passwordErrorMassage = "";
    }
  }
};
</script>
```

メールアドレスとパスワードのinputタグに`@input="isInput"`を追記し、入力を始めると`methods.isInput`が発火して、エラーメッセージを表示しないようにすることができます。

>参考記事：https://qiita.com/kj455/items/8e6b5fe3755cab7ef73e#%E3%82%B3%E3%83%BC%E3%83%89

<br>


#おわりに
ここまでFirebase Authで正規表現を用いたバリデーションを行う方法についてまとめました。
ユーザに使ってもらうサービスなら当たり前かもしれませんが、安全性と操作性は常に考慮するべきことだと思うので、細かいところまで気を配れるように丁寧に進めていきます！
以上、最後まで読んでいただきありがとうございました！
よければLGTMを押してくれると嬉しいです！