8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Nuxt3 + Pinia + firebase v9で認証処理を作ってみる

Last updated at Posted at 2022-09-30

はじめに

Nuxt3 と firebase v9 を使って認証フローを作成してみました。
認証データの状態管理には Pinia を使用します。

firebaseの接続情報

接続情報は publicRuntimeConfig にまとめておきます。

nuxt.config.ts
export default defineNuxtConfig({
  ...
  modules: [
    ['@pinia/nuxt', { autoImports: ['defineStore'] }],
  ],
  publicRuntimeConfig: {
    firebase: {
      apiKey: 'xxxxxxxxxxxxxxxxxxxx',
      authDomain: 'xxxxxxxxxxxxxxxxxxxx',
      projectId: 'xxxxxxxxxxxxxxxxxxxx',
      storageBucket: 'xxxxxxxxxxxxxxxxxxxx',
      messagingSenderId: 'xxxxxxxxxxxxxxxxxxxx',
      appId: 'xxxxxxxxxxxxxxxxxxxx',
    },
  },
})

Storeに認証処理を記述します

signIn / signOut 処理自体はごくシンプルに firebase/auth を呼び出すだけに留めておき、state更新はonAuthStateChangedハンドラで行うことにします。

store/auth.ts
import { navigateTo } from '#app'
import { defineStore } from 'pinia'
import {
  getAuth,
  onAuthStateChanged,
  signInWithEmailAndPassword,
  signOut,
} from 'firebase/auth'

export const useAuthStore = defineStore('auth', {
  state: () => ({
    displayName: '',
    email: '',
    idToken: '',
  }),
  getters: {
    isLoggedIn: (state): boolean => !!state.idToken,
  },
  actions: {
    initAuthStateChangedHandler() {
      onAuthStateChanged(getAuth(), (user) => {
        if (!user) {
          this.$reset()
          navigateTo('/login', { replace: true })
          return
        }

        user.getIdToken().then((idToken) => {
          const { displayName, email } = user
          this.$patch({
            idToken,
            displayName: displayName || '',
            email: email || '',
          })

          const redirectTo = useRoute().redirectedFrom?.path || '/'
          navigateTo(redirectTo, { replace: true })
        })
      })
    },
    signOut(): Promise<void> {
      return signOut(getAuth())
    },
    async signIn(email: string, password: string): Promise<void> {
      await signInWithEmailAndPassword(getAuth(), email, password)
    },
  },
})

Firebaseの初期化

firebase v9 は @nuxtjs/firebase が対応していないので、Firebase 初期化はpluginで行います。

onAuthStateChanged の登録処理は、plugin内でFirebaseAppオブジェクトの作成した後で、続けて呼び出しておく事にします。
Firebase の初期化前にハンドラーが呼び出されることを避けるためにこのようにしています。

plugins/firebase.client.js
import { useAuthStore } from '~/stores/auth'

export default defineNuxtPlugin(() => {
  const config = useRuntimeConfig()

  initializeApp({
    apiKey: config.public.firebase.apiKey,
    authDomain: config.public.firebase.authDomain,
    projectId: config.public.firebase.projectId,
    storageBucket: config.public.firebase.storageBucket,
    messagingSenderId: config.public.firebase.messagingSenderId,
    appId: config.public.firebase.appId,
  })

  useAuthStore().initAuthStateChangedHandler()
})

ページ自動遷移処理

ログイン状態による自動遷移は global middleware で定義しておきます。
isLoggedIn は onAuthStateChanged によって更新されるので、 backend で更新トークンが無効化された際に自動でログインページに遷移すると期待します。(まだ試していませんが)

middleware/auth.global.ts
import { useAuthStore } from '~/stores/auth'
import { navigateTo } from '#app'

export default defineNuxtRouteMiddleware(async (to, _from) => {
  const { isLoggedIn } = useAuthStore()

  if (isLoggedIn && to.path === '/login') {
    return navigateTo('/')
  }

  if (!isLoggedIn && to.path !== '/login') {
    return navigateTo('/login', { replace: true })
  }
})

ここまでが 画面遷移時に session の自動確認を行う手法です。
更に認証が必要な画面が表示されっぱなしにならないようにする場合は plugin でイベントを追加して対応します。
今回はタブ表示切り替えと定時実行に対応します。

plugins/session.client.ts
import { defineNuxtPlugin } from '#app'
import { useAuthStore } from '~/stores/auth'

export default defineNuxtPlugin(() => {
  const { isLoggedIn, signOut } = useAuthStore()
  const signOutWhenSessionIsOver = async (): Promise<void> => {
    if (document.visibilityState === 'visible' && isLoggedIn) {
      await signOut()
    }
  }

  document.addEventListener('visibilitychange', signOutWhenSessionIsOver)

  window.setInterval(signOutWhenSessionIsOver, 60000)
})

おわりに

Nuxt3は自動importが効くので、実際はもっとソース量を圧縮できるのですが、明示的にimportするとideaの型推測サポートが効いたので、今回はあえて書くことにしました。
それでもNuxt2に比べると書く量がだいぶ減った印象を持ちました。TypeSafeなPiniaが使用できる恩恵も大きかったです。

8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?