Nuxt.js の 外部認証サービスとの連携は Cookie の expires を設定する（Universal）

## 運用しているサービスの認証周りでバグ発生

[ShoTime（大谷翔平選手が書いていたと噂の目標達成シート）](https://shotime.now.sh/)にて原因究明に１ヶ月もかかってしまった認証周りの問題がありました。たまにしか発生しないエラーだったため、発見が難しく、あまり気にしなくて良いやという気持ちがまさり、１ヶ月も経過してしまいました。同じミスを繰り返さないために、ここに記録しておきます。

## Nuxt.js における外部認証サービスとの連携例

Nuxt.js で 認証を作る例として、次のようなコードが公式のドキュメントに掲載されています。この例を参考に実装しサービスを運用していのですが、たまにエラーページが表示され、リロードすると解決するという問題が発生しました。

https://nuxtjs.org/examples/auth-external-jwt#login-page

```js
const Cookie = process.client ? require('js-cookie') : undefined

export default {
  middleware: 'notAuthenticated',
  methods: {
    postLogin() {
      setTimeout(() => { // we simulate the async request with timeout.
        const auth = {
          accessToken: 'someStringGotFromApiServiceWithAjax'
        }
        this.$store.commit('setAuth', auth) // mutating to store for client rendering
        Cookie.set('auth', auth) // saving token in cookie for server rendering
        this.$router.push('/')
      }, 1000)
    }
  }
}
```


 `Cookie.set('auth', auth)` と書いた場合、ブラウザを閉じるまでがクッキーの保存期間です。（以降、ブラウザを閉じるまでの期間を「セッション」とします）

> Default: Cookie is removed when the user closes the browser.

[参照: js-cookie GitHub リポジトリより](https://github.com/js-cookie/js-cookie#cookie-attributes)


## 運用していたサービスの概要

- Nuxt.js v2.2
- Universal SSR
- Firebase Authentication

## 外部認証サービスとブラウザとバックエンド

外部認証サービスではセッションを有効期間としていないこともよくあります。その場合、Cookie は有効期限切れで存在しないにも関わらず、認証サービスは有効になり、不整合が発生してしまいます。

具体的には、次のような状況で発生し、原因究明に時間を要しました。

1. **クライアント:** ログインする
2. **クライアント:** セッションを切る（ブラウザを閉じる）
3. **クライアント:** 認証済みページを表示しようとする（リクエストをおくる）
4. **バックエンド:** 
    1. nuxtServerInit にてリクエストの処理
    2. Cookie に認証情報がないか確認するが存在しない
    3. Store に認証情報がないと記録する（commit）
    4. 認証済みじゃないと閲覧できないページを表示させようするが（middleware）
    5. 認証が必要ないページへリダイレクトされる（middleware）
    6. 認証が必要ないページをクライアントへ返す
5. **クライアント:** 
    1. 認証不要で閲覧できるページが返ってくる
    2. 認証サービスでは認証済みになっているが、認証不要ページしか閲覧できない
6. リロードをすると認証が必要なページも閲覧できる

フローを手書きで図にしてみました。（汚い字ですが...）
![IMG_2691.jpg](https://qiita-image-store.s3.amazonaws.com/0/62259/4c3dafa4-e467-bcf3-8cd9-bf9a5d75807d.jpeg)

6でリロードをするのではなく、認証済みで閲覧可能なページへリダイレクトさせる方法も考えたのですが、２点問題があります。

- **１回目のレスポンス後、middleware はブラウザ側で動作しない**ため、全ての　Pageコンポーネントに記述が必要
- どこのページを表示しようとしていたのか、この時点では分からなくなってしまっている

## Cookie をセットする際に expires を正しく記述すれば解決

expires を正しく設定しておかないと middleware の認証まわりで狙った動作ができないことがあります。expires は、外部認証サービスと合わせて同じ期間をせってしておくと安心です。

## Cookie の設定例

ここが結論で全てです。

```js
Cookie.set('auth', currentUser, { expires: 3 }) // 3 days
```

## Nuxt.js middleware の挙動を追う

**最初のリクエストでは、middleware がバックエンドで実行されます。**
２回目以降のページ遷移では、middleware がフロントエンドで実行されます。

**１回目のリクエストでは、middleware がバックエンドで実行される**というのがポイントで、リクエストに正しい Cookie が入っていない場合、バックエンドでは非認証の状態だと判断してしまいます。そして、１回目のリクエストではクライアントサイドで middleware が実行されないため、バックエンドからのレスポンスをそのままブラウザで表示してしまいます。

## 認証周りについてはサンプルが少ない

GitHub でコードを読み漁ってベストプラクティスを求めて認証周りを書いていたつもりでしたが、Cookie については情報がありませんでした。もしかしたら常識だから情報がなかったのかもしれませんが、私はこの問題究明に１ヶ月くらいかかってしまいました。。

## この問題は、Nuxt.js のモードが Universal SSR の時だけ?
Nuxt.js のモードがユニバーサルではなく、SPA や Generate であれば、この問題は関係がないのかもしれません。

## まとめ
Nuxt.js の Universal SSR は考慮すべき点が他にもいくつかあるため、求める仕様が他のモードで対応できる場合は、私はユニバーサルにしないと思います。特に middleware の役割を理解することや、コンポーネントにおける created mounted など、どこで実行されるのか掴むことが大切です。

とりあえずサービスは動いていますが、認証まわりのコードにはまだ不安が残っている状態です。また、**この記事での対応は本当に正しいかも怪しい**です。ユニバーサルモードは、バックエンドが絡んでくるため、GitHub で公開されているコードも少ないのかもしれませんが、需要あると思います。有料でも具体的なコードがあれば買いたいです。

修正やご指摘などお気軽にください。コメントするまでもないけど気になる点などございましたら Twitter の DM でご連絡いただければと思います。もしこの記事が間違った情報であれば、正しい情報に修正したいので、お手間おかけしますが、よろしくお願いします。

## 他のカレンダーも書いています

2018年のインプットは2018年のうちにアウトプットすることも目標に、こちら（[ShoTime Advent Calendar 2018](https://qiita.com/advent-calendar/2018/shotime)）でも Nuxt.js や JavaScript などの記事書いています。もしよろしければご覧ください。