Nuxt.jsでAuth Moduleを使ってログイン機能を実装する

Nuxt.jsを使ったプロジェクトで、Auth Moduleを使ってJWT認証を実装したので、忘れないうちにまとめておきます。

ざっくり流れを説明すると、

* クライアント側からサーバー側へメールアドレスとパスワードを送信する
* サーバー側はトークンを返す

というシンプルなものです。
サーバーが返したトークンは、クライアント側がAPIを叩く際にヘッダーに付与して使用します。

# ログイン時のリクエストとレスポンス
クライアント側は `api/login` に下記のbodyを持ったPOSTリクエストを送信するものとします。

```json
{
  "email": "example@example.xom",
  "password": "password"
}
```
サーバー側はレスポンスとして、ヘッダーに付与するためのトークンを返します。

```json
{
  "token": "xxxxxxxxxxxxxx"
}
```

# ユーザー情報を返すAPIを用意する
ヘッダーに `Authorization: Bearer xxxxxxxxxxxxxx` を付与して `api/me` にGETリクエストを送ることにより、下記のようなユーザー情報を返すAPIを用意します（内容は一例です）。

```json
{
  "id": 1,
  "name": "tanaka hiroshi",
  "age": "32"
}
```

# Auth Moduleを導入する

[Auth Module](https://auth.nuxtjs.org/)を導入します。

```shell
npm install --save @nuxtjs/auth
```

Auth Moduleはvuex storeを使用するため、storeを使用していない場合は `store/index.js` という空のファイルを作成しましょう。

そして `nuxt.config.js` に設定を追加します。

```nuxt.config.js
modules: [
  '@nuxtjs/auth'
],
```

# Auth Moduleの設定を行う

`nuxt.config.js` にAuth Moduleの設定を追加しましょう。
設定の詳細な内容は[こちらのドキュメント](https://auth.nuxtjs.org/api/options.html#redirect)を確認してください。

```nuxt.config.js
export default {
  auth: {
    redirect: {
      login: '/login',   // 未ログイン時に認証ルートへアクセスした際のリダイレクトURL
      logout: '/login',  // ログアウト時のリダイレクトURL
      callback: false,   // Oauth認証等で必要となる コールバックルート
      home: '/',         // ログイン後のリダイレクトURL
    },
    strategies: {
      local: {
        endpoints: {
          login: { url: 'api/login', method: 'post', propertyName: 'token' },
          user: { url: 'api/me', method: 'get', propertyName: false},
          logout: false
        },
      }
    }
  },
}
```
以上で設定完了です！

# ログイン機能の作成

サンプルとして、下記のようなログインページを実装します。

login関数の `this.$auth.loginWith('local', { data: this.form });` の部分でログインを実行します。
これを実行すると、 先程 `nuxt.config.js` で設定した `api/login` と `api/me` が叩かれ、ヘッダーに付与するtokenとuser情報がstoreに保存されます。
その後、 ログイン後のページにリダイレクトされます。

`store.$auth.loggedIn` でログインしているか否かを確認できるので、 middlewareにログイン判定を実装して、ログイン済みならログイン後のページへリダイレクトするようにしています。

また、 `store.$auth.getToken('local');` でヘッダーに付与するtokenが取得できるため、axiosの共通設定部分に指定して使用することもできます。
参考： [Nuxt.jsでaxiosの共通処理を作成し、API呼び出し処理をラップして使用する](https://qiita.com/itouuuuuuuuu/items/4132e3b7ddf2cbf02442)

```pages/login.vue
<template lang="pug">
.login-container
  el-form(:model="form")
    el-form-item(label="メールアドレス")
      el-input(v-model="form.email")
    el-form-item(label="パスワード")
      el-input(v-model="form.password" type="password")
    el-button(type="primary" @click="login") ログイン
</template>

<script>
export default {
  middleware({ store, redirect }) {
    if(store.$auth.loggedIn) {
      redirect('/');
    }
  },
  data() {
    return {
      form: {
        email: '',
        password: ''
      }
    }
  },
  methods: {
    async login() {
      try {
        const response = await this.$auth.loginWith('local', { data: this.form });
        console.log(response);
      } catch(error) {
        console.log(error);
      }
    }
  }
}
</script>

<style lang="scss">
.login-container {
  margin: 50px auto;
  width: 300px;
  text-align: center;
}
</style>
```

<img width="393" alt="スクリーンショット 2019-10-13 12.54.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279032/0fc6df0a-a30f-d451-f54c-1c6cc444de1f.png">

ちなみに、`nuxt.config.js`に設定したログイン後のリダイレクト先を`home: false`にすることで、ログイン後のリダイレクトのタイミングを任意に制御できます。

```pages/login.vue
<script>
methods: {
  async login() {
    try {
      const response = await this.$auth.loginWith('local', { data: this.form });
      console.log(response);

      // ここでホームへリダイレクトする前に行いたい処理

      this.$router.replace({ path: '/' }); // 任意のタイミングでリダイレクト
    } catch(error) {
      console.log(error);
    }
  }
}
</script>
```

# ログアウト機能の作成

サンプルとして、下記のようなログイン済みページを実装します。

logout関数の `this.$auth.logout();` の部分でログアウトを実行します。
これを実行すると、storeに保存されたuser情報が破棄され、ログインページにリダイレクトされます。

ログインページと同様に `store.$auth.loggedIn` でログインしているか否かを確認して、 middlewareにログイン判定を実装しています。
ログインしていなかったらログインページへリダイレクトするようにしています。

また、 `this.$auth.user` でuser情報を取得することもできます。

```index.vue
<template lang="pug">
.container
  p 名前：{{ user.name }}
  p 年齢：{{ user.age }} 歳
  el-button.button(type="primary" @click="logout") ログアウト
</template>

<script>
export default {
  middleware({ store, redirect }) {
    if(!store.$auth.loggedIn) {
      redirect('/login');
    }
  },
  computed: {
    user() {
      return this.$auth.user;
    }
  },
  methods: {
    logout() {
      this.$auth.logout();
    },
  }
}
</script>

<style lang="scss">
.container {
  margin: 50px auto;
  width: 300px;
  text-align: center;

  .button {
    margin-top: 30px;
  }
}
</style>
```
<img width="389" alt="スクリーンショット 2019-10-13 13.06.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279032/32dfcec3-91d3-e54f-2aad-e88ecd444c4a.png">

以上で実装完了です。
簡単にログイン/ログアウト処理が実装できるので、とてもオススメです！