onAuthStateChangedをVueのナビゲーションガードに書いたらあらぬところにルーティングされた // Firebase Vue

#経緯
FirebaseAuthを利用してログアウトするとthis.$router.push('/')によってホームに遷移するように実装したところ、何故かログインページに飛ばされてしまった。
##vue-routerの中身

```javascript:router/index.js
//ナビゲーションガード
router.beforeEach((to, from, next) => {
  const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
  console.log("A")
  if (requiresAuth) {
    console.log("B")
    firebase.auth().onAuthStateChanged(async user => {
      console.log("C")
      if (user) {
        await user.reload()
        if (user.emailVerified) {
          //ログインとメール有効化が済んでいるため通過させる
          console.log("D")
          next()
        } else {
          //メール有効化が済んでいないためメール認証ページに遷移させる
          console.log("E")
          next({ path: '/emailverification', query: {redirect: to.fullPath}})
        }
      } else {
        //ログインを促す
        console.log("F")
        next({ path: '/signin', query: {redirect: to.fullPath}})
      }
    })
  } else {
    //認証の必要なし
    console.log("G")
    next()
  }
})
```

#コンソール結果
ホーム（'/'）は認証の必要がないページなのでconsoleの表示は

```console:console
A
G
```

になるはず...だが、実際は

```console:console
C
F
A
G
```

と表示されてログインページに飛ばされている
あれれjavascriptって上から処理されるんじゃないの!?

#onAuthStateChangedはオブザーバーである
コンソールによればonAuthSteteChangedの中身が先に実行され、後からナビゲーションガードが上から実行されているようなので、onAuthStateChangedを調べることにした。
[Firebaseリファレンス -onAuthStateChanged](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onauthstatechanged)

>ユーザーのサインイン状態を変更するためのオブザーバーを追加します。
4.0.0より前のバージョンでは、ユーザーがサインインまたはサインアウトしたとき、またはトークンの有効期限やパスワードの変更などの状況でユーザーのIDトークンが変更されたときに、オブザーバーがトリガーされました。 4.0.0以降、オブザーバーはサインインまたはサインアウト時にのみトリガーされます。

オブザーバーがよくわからなかったので調べると要素の変更を検出して呼び出される処理方法のことらしい。
この場合はuserのサインイン・アウトを検知して中身が処理されるということらしい。
つまり、ログアウトした瞬間userの変更を検知して中身が実行が優先され、ログインページに飛ばされたということ。

#ではどうするか
####失敗例① beforeEachのスコープ外や他のjsファイルにオブザーバーを記述
この場合onAuthStateChangedオブザーバーの中身が呼び出されるタイミングに一貫性が見られず、上記同様にあらぬところに飛ばされることが多かった。

####失敗例② firebase.auth().currentUserを用いる
currentUserはブラウザ更新時にnullを返すため、いきなりログインページに飛ばされてしまう。

####検討例① 永続性のあるvuex-persistedstate(WebStorage)の活用
ログイン情報の保持にWebStorageを用いる例は一般的らしいが、セキュリティに問題があるとの報告もあり今回は断念した。
[Web Storage (localStorage, sessionStorage)にセッション情報を置くのをやめよう]
(https://qiita.com/togana/items/57907fc32ca19136946d)
[HTML5のLocal Storageを使ってはいけない（翻訳）]
(https://techracho.bpsinc.jp/hachi8833/2019_10_09/80851)

####検討例② Cookieを用いる
なんか難しそうだから断念した。わからないままやっても危険だし...
公式のindexDBでいいじゃんか...

#onAuthStateChangedをその都度unsubscribeして用いる
この厄介なオブザーバーの欠点は中身が呼ばれるタイミングが掴めないこと。
なので、オブザーバーによる監視を都度解除しながら使う方法が最適だった。
[Vue, guard routes with Firebase Authentication]
(https://dev.to/gautemeekolsen/vue-guard-routes-with-firebase-authentication-f4l)

firebase.jsにuserのPromiseを返すメソッドを記述する。

```javascript:plugins/firebase.js
firebase.getCurrentUser = () => {
  return new Promise((resoleve, reject) => {
    const unsubscribe = firebase.auth().onAuthStateChanged(user => {
      unsubscribe()
      resoleve(user)
    }, reject)
  })
}
```

あとはこれをナビゲーションガードから用いるだけ。

```javascript:router/index.js
//ナビゲーションガード
router.beforeEach(async (to, from, next) => {
  const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
  if (requiresAuth) {
    const user = await firebase.getCurrentUser()
    if (user) {
      await user.reload()
      if (user.emailVerified) {
        next()
      } else {
        next({ path: '/emailverification', query: {redirect: to.fullPath}})
      }
    } else {
      next({ path: '/signin', query: {redirect: to.fullPath}})
    }
  } else {
    next()
  }
})
```

email有効化を無視するならもっと簡潔になる。

```javascript:router/index.js
//ナビゲーションガード
router.beforeEach(async (to, from, next) => {
  const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
  if (requiresAuth && !(await firebase.getCurrentUser())) {
    next({ path: '/signin', query: {redirect: to.fullPath}})
  } else {
    next()
  }
})
```

できました！