Nuxt.js + Firebase + Netlifyでログイン認証＋push通知機能のあるPWAを作る(その2) - ログイン処理、ユーザ登録、認証処理実装

第１回と第３回の記事はこちらになります
- 第1回
[Nuxt.js + Firebase + Netlifyでログイン認証＋Push通知機能のあるPWAを作る(その1) - 環境構築(Nuxt、Firebase設定、Netlifyホスティング、自動デプロイ) - Qiita](https://qiita.com/shinbey221/items/ecead669daa56a491d7a)
- 第3回
[Nuxt.js + Firebase + Netlifyでログイン認証＋push通知機能のあるPWAを作る(その3) - Push通知受け取り&amp;Functionsを使ってRealtimeDatabaseにトークンを保存 - Qiita](https://qiita.com/shinbey221/items/bc7f20f5b47daf047f6e)

この回ではログイン画面、ユーザ登録画面からFirebase Authenticateを使用してログイン機能、ユーザ登録機能を実装していきます。
UIフレームワークはBootstrap Vueを使用します。(プロジェクト作成時に設定済み)

### Vuexの実装

ログインしたユーザ情報をVuexのStoreに保存するためにaction,mutation,stateをStoreに定義します。
 (Vuexの説明については割愛)

#### Firebaseのログイン、ログアウト、ユーザ登録処理

まずはFirebaseのログイン、ログアウト、ユーザ登録の実装を行います。
それぞれをメソッドとして定義します。

`store/user.js`

```javascript
import firebase from '@/plugins/firebase'

const firebaseLogin = (user) => {
  return new Promise((resolve, reject) => {
    firebase
      .auth()
      .signInWithEmailAndPassword(user.email, user.password)
      .then((data) => {
        const { displayName, email } = data.user
        resolve({ displayName, email })
      })
      .catch((error) => {
        reject(error)
      })
  })
}

const firebaseLogout = (user) => {
  return new Promise((resolve, reject) => {
    firebase
      .auth()
      .signOut()
      .then(() => {
        resolve()
      })
      .catch((error) => {
        reject(error)
      })
  })
}

const firebaseCreateUser = (user) => {
  return new Promise((resolve, reject) => {
    firebase
      .auth()
      .createUserWithEmailAndPassword(user.email, user.password)
      .then((data) => {
        resolve(data)
      })
      .catch((error) => {
        reject(error)
      })
  })
}

```


#### state

保存先としてuserDataを定義。

`store/user.js`

```javascript
export const state = () => ({
  userData: null
})
```


#### getter

stateを参照するgetterを定義。
stateにユーザ情報があるかの判定を行うisAuthenticatedとstateのユーザ情報を取得するgetUserDataを定義します。

`store/user.js`

```javascript
export const getters = {
  isAuthenticated(state) {
    return !!state.userData
  },
  getUserData(state) {
    return state.userData
  }
}
```

#### mutation

mutationにはユーザデータをstateにセットする処理を定義。

`store/user.js`

```javascript
export const mutations = {
  setUser(state, payload) {
    state.userData = payload
  }
}
```


#### action

ログイン、ログアウト、ユーザ登録、ユーザ情報セットのactionの定義を行います。
ユーザ登録に関してはユーザ登録が完了したタイミングでログインも行うようにします。

`store/user.js`

```javascript
export const actions = {
  async login({ commit }, user) {
    try {
      const loginResult = await firebaseLogin(user)
      commit('setUser', loginResult)
      return
    } catch (e) {
      throw new Error(e)
    }
  },
  async logout({ commit }) {
    try {
      await firebaseLogout()
      return
    } catch (e) {
      throw new Error(e)
    }
  },
  async createUser({ commit }, user) {
    try {
      const createLogin = await firebaseCreateUser(user)
      const resultLogin = await firebaseLogin(user)
      commit('setUser', null)
      return resultLogin
    } catch (e) {
      throw new Error(e)
    }
  },
  setUserData({ commit }, user) {
    commit('setUser', user)
  }
}
```


### ログイン画面

#### 画面デザイン

画面構成は以下のようにします

- 「LOGIN」のタイトル
- メールアドレス入力欄
- パスワード入力欄
- ログインボタン
- ユーザ登録画面遷移リンク

`pages/login.vue`

```javascript
<template>
  <div>
    <b-container fluid>
      <b-row class="text-center" align-v="center">
        <b-col cols="1" md="5" />
        <b-col cols="10" sm="10" md="2">
          <h1 style="margin-top: 60px;">LOGIN</h1>
          <b-form style="margin-top: 30px;" @submit="signIn">
            <b-form-group label="Email address" label-for="input-1">
              <b-form-input
                id="input-1"
                type="email"
                required
                placeholder="Enter email"
                v-model="email"
              ></b-form-input>
            </b-form-group>
            <b-form-group label="Password" label-for="input-2">
              <b-form-input
                id="input-2"
                type="password"
                required
                placeholder="Enter password"
                v-model="password"
              ></b-form-input>
            </b-form-group>
            <b-form-group style="margin-top: 30px;">
              <b-button block type="submit" variant="success">
                Sign In
              </b-button>
            </b-form-group>
          </b-form>
          <nuxt-link to="/createAccount">create account</nuxt-link>
        </b-col>
        <b-col cols="1" />
      </b-row>
    </b-container>
  </div>
</template>

<script>
export default {
  data() {
    return {
      email: '',
      password: ''
    }
  },
  methods: {
    signIn(event) {
      event.preventDefault()
      console.log('sign in.')
    }
  }
}
</script>

```

<img width="873" alt="スクリーンショット 2019-07-30 10.21.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/442896/5954a22e-1be9-36d6-ca9a-108eb40fa125.png">




#### ログイン処理実装

VuexのアクションからFirebaseログイン→トップに遷移の実装を行います。
actionのhelperをインポート。

```javascript
import { mapActions } from 'vuex'
```

methods内にaction helperを用いて使用アクションの定義を行います。

```javascript
...mapActions('user', ['login'])
```


signInメソッド内にactoinのloginを叩いてログインを行い、正常に処理が通ればトップに遷移する処理を実装します。

```javascript
signIn(event) {
  event.preventDefault()
  const user = {
    email: this.email,
    password: this.password
  }
  this.login(user)
    .then(() => {
      this.$router.push('/')
    })
    .catch((error) => {
      alert(error)
    })
}
```

ここまで実装できたらFirebaseコンソールのAuthenticationの画面を開き、`ユーザの追加`というボタンがあるので選択し、メールアドレスとパスワードを入力しユーザを追加します。

追加したユーザのメールアドレスとパスワードを使って実装したログイン画面からログインが可能になっています。


### ユーザ登録画面

#### 画面デザイン

画面構成は以下のようにします

- 「Create Account」のタイトル
- メールアドレス入力欄
- パスワード入力欄
- パスワード確認入力欄
- サインアップボタン

`pages/createAccount.vue`

```javascript
<template>
  <div class="loginbackgroud">
    <b-row class="text-center" align-v="center">
      <b-col cols="1" md="4" />
      <b-col cols="10" sm="10" md="4">
        <h1 style="margin-top: 60px;">Create Account</h1>
      </b-col>
    </b-row>
    <b-container fluid>
      <b-row class="text-center" align-v="center">
        <b-col cols="1" md="5" />
        <b-col cols="10" sm="10" md="2">
          <b-form style="margin-top: 30px;" @submit="signUp">
            <b-form-group label="Email address" label-for="input-1">
              <b-form-input
                id="input-1"
                type="email"
                required
                placeholder="Enter email"
                v-model="email"
              ></b-form-input>
            </b-form-group>
            <b-form-group label="Password" label-for="input-2">
              <b-form-input
                id="input-2"
                type="password"
                required
                placeholder="Enter password"
                v-model="password"
              ></b-form-input>
            </b-form-group>
            <b-form-group label="ConfirmPassword" label-for="input-2">
              <b-form-input
                id="input-2"
                type="password"
                required
                placeholder="Enter confirm password"
                v-model="confirmPassword"
              ></b-form-input>
            </b-form-group>
            <b-form-group style="margin-top: 30px;">
              <b-button block type="submit" variant="success">
                Sign Up
              </b-button>
            </b-form-group>
          </b-form>
        </b-col>
        <b-col cols="1" />
      </b-row>
    </b-container>
  </div>
</template>

<script>
import firebase from 'firebase'
import { mapActions } from 'vuex'

export default {
  data() {
    return {
      email: '',
      password: '',
      confirmPassword: ''
    }
  },
  methods: {
    signUp() {
      event.preventDefault()
      console.log('sign up.')
    }
  }
}
</script>

```

<img width="851" alt="スクリーンショット 2019-07-30 10.22.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/442896/32a49a17-d340-6b00-dfa5-9de8b7b60de2.png">



#### ユーザ登録処理実装

VuexのアクションからFirebaseユーザ登録→ログイン→トップに遷移の実装を行います。
actionのhelperをインポート。

```javascript
import { mapActions } from 'vuex'
```

methods内にaction helperを用いて使用アクションの定義を行います。

```javascript
...mapActions('user', ['createUser'])
```

signUpメソッド内にactoinのcreateUserを叩いてユーザ登録、ログインを行い、正常に処理が通ればトップに遷移する処理を実装します。

```javascript
signUp(event) {
  event.preventDefault()
  if (this.password !== this.confirmPassword) {
    alert('Not match confirm password.')
    return
  }
  const user = {
    email: this.email,
    password: this.password
  }
  this.createUser(user)
    .then(() => {
      this.$router.push('/')
    })
    .catch((error) => {
      alert(error)
    })
}
```

ここで実際に画面からメールアドレス、パスワードを入力し、submitすると一連の処理が正常に処理され、トップに遷移します。
FirebaseコンソールのAuthenticationのユーザを開くと先ほど入力したユーザが登録されます。



### middlewareでログイン認証

#### middleware 設定

Nuxt.jsのmiddlewareの機能を使ってログイン済みかそうでないかの判定を行います。
Nuxtのmiddlewareはページ(パス)が変わりレンダリングされる前に実行する処理を実装することができる機能です。
`middleware`ディレクトリに`authenticated.js`を作成。

```
touch middleware/authenticated.js
```

`nuxt.config.js`にmiddlewareの登録を行います。

```javascript
export default {
  router: {
    middleware: 'authenticated'
  }
}
```


#### ログイン認証の実装

ここからログイン認証済みかそうでないかの処理を実装します。
FirebaseのonAuthStateChangedを使うとログイン済みであればユーザの情報が返ってくるのでこれを使用。
処理としては

- ログインしていない場合はログインページ、ユーザ登録ページ以外のページには飛ばないようにする
- ログイン済みの場合でVuexのstoreにログイン情報がない場合はstateに保存する
- ログイン済みの場合でログインページ、ユーザ登録ページに遷移しないようにする

これらを満たす処理を実装します

```javascript
import firebase from 'firebase'

const auth = async ({ store, route, redirect }) => {
  const user = await new Promise((resolve, reject) => {
    firebase.auth().onAuthStateChanged((user) => resolve(user))
  })
  if (!user && !(route.name === 'login' || route.name === 'createAccount')) {
    redirect('/login')
  }
  if (user && !store.getters['user/isAuthenticated']) {
    const { displayName, email } = user
    store.dispatch('user/setUserData', { displayName, email })
  }
  if (user && (route.name === 'login' || route.name === 'createAccount')) {
    redirect('/')
  }
}

export default auth
```

これでログイン済みであればリロードしてもトップページに遷移してくれるようになりました。

### まとめ

ログイン処理周りはFirebaseAuthentication使えばかなり簡単に実装ができます。
またNuxtのmiddlewareを使うことでログイン認証も簡単に実装できました。
Nuxtはmiddlewareの他にもルーティング簡単にできたりasyncDataメソッドとか便利な機能が多くていいですね。

次回はFirebase Cloud Messagingを使ってプッシュ通知の実装を行います。