技術ブログ

(技術系中心)基本自分用備忘録なので、あくまで参考程度でお願いします。

Nuxt3とfirebase9で認証機能設定する方法

Nuxt3とfirebase9で認証機能設定する方法

各バージョン

Nuxt: 3.0.0-rc.11
Firebase: 9.13.0

インストール

npm install firebasepackage.jsonfirebaseが追加されればOK

Firebaseの初期設定

■ plugins/firebase.client.ts

import { initializeApp } from 'firebase/app'
import { defineNuxtPlugin } from '#app'

export default defineNuxtPlugin(() => {
  const config = useRuntimeConfig()
  const firebaseConfig = {
    apiKey: config.FIREBASE_API_KEY,
    authDomain: config.FIREBASE_AUTH_DOMAIN,
    projectId: config.FIREBASE_PROJECT_ID,
    storage_bucket: config.FIREBASE_STORAGE_BUCKET,
    messaging_sender_id: config.FIREBASE_MESSAGING_SENDER_ID,
    api_id: config.FIREBASE_API_ID,
    measurement_id: config.FIREBASE_MEASUREMENT_ID,
  }
  initializeApp(firebaseConfig)
})

(Authenticationのみで利用するので、初期化時の引数はapiKeyとauthDomainとprojectIdの3つを最低限指定しておけば問題なく動く。)

  • plugins ディレクトリについて
  • pluginsディレクトリ配下のjsファイルは自動的に読まれるので、nuxt.config.jsへの設定は不要。
  • defineNuxtPluginというplugin読み込み用の関数が用意されているので、これを使う。
  • プラグイン内でランタイムの設定を使用したい場合は、defineNuxtPlugin 関数内でuseRuntimeConfig() を使用する

Nuxt3はdotenvが内包されているので.envでお手軽に環境変数を設定する

■ .env

FIREBASE_API_KEY=****************
FIREBASE_AUTH_DOMAIN=****************
FIREBASE_PROJECT_ID=****************
FIREBASE_STORAGE_BUCKET=****************
FIREBASE_MESSAGING_SENDER_ID=****************
FIREBASE_API_ID=****************
FIREBASE_MEASUREMENT_ID=G-****************

nuxt3には、実行時に設定値をロードするための仕組みとしてruntimeConfigというものがある。コンポーネント内からはuseRuntimeConfigや$configで値にアクセスすることができる。

■ nuxt.config.ts

  runtimeConfig: {
    public: {
      FIREBASE_API_KEY: process.env.FIREBASE_API_KEY || '',
      FIREBASE_AUTH_DOMAIN: process.env.FIREBASE_AUTH_DOMAIN || '',
      FIREBASE_PROJECT_ID: process.env.FIREBASE_PROJECT_ID || '',
      FIREBASE_STORAGE_BUCKET: process.env.FIREBASE_STORAGE_BUCKET || '',
      FIREBASE_MESSAGING_SENDER_ID: process.env.FIREBASE_MESSAGING_SENDER_ID || '',
      FIREBASE_API_ID: process.env.FIREBASE_API_ID || '',
      FIREBASE_MEASUREMENT_ID: process.env.FIREBASE_MEASUREMENT_ID || '',
    },
  },

例)config.FIREBASE_API_KEYと記載すれば.envで設定されたFIREBASE_API_KEYの値が取得できる

各認証メソッド

認証の処理はcomposableにまとめる。

Composables directoryではこちらのドキュメントの通り、各Componentでimportを書かなくても呼び出しができるものになります。

■ composables/useAuth.ts

import {
  getAuth,
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  signOut as firebaseSignOut,
  onAuthStateChanged,
  GoogleAuthProvider,
  signInWithPopup,
  TwitterAuthProvider
} from 'firebase/auth'

export const useAuth = () => {
  const token = useState<string>('token', () => null)

  // メールアドレス新規登録関数
  async function signUp(email:string, password:string){
    return await new Promise((resolve)=>{
      // getAuth()でAuthを取得
      const auth = getAuth()
      // メールアドレスとパスワードでアカウントを作成する
      createUserWithEmailAndPassword(auth, email, password)
      .then((userCredential) => {
        console.log(userCredential)
        // サインアップできたらログインする
        resolve("success")
      })
      .catch((error) => {
        console.log(error)
        const errorMessage = error.message;
        resolve(errorMessage)
      })
    })
  }

  // メールアドレスとパスワードでログインする関数
  async function signIn(email: string, password: string) {
    return await new Promise<void>((resolve, reject) => {
      const auth = getAuth()
      // メールアドレスとパスワードでログインする
      return signInWithEmailAndPassword(auth, email, password)
        .then((userCredential) => {
          userCredential.user
            .getIdToken()
              .then((idToken) => {
                token.value = idToken
                resolve()
              })
            .catch(reject)
        })
        .catch(reject)
    })
  }

  // ログアウトする関数
  async function signOut() {
    return await new Promise<void>((resolve, reject) => {
      const auth = getAuth()
      // ログアウトする
      firebaseSignOut(auth)
        .then(() => {
          token.value = null
          resolve()
        })
        .catch((error) => {
          reject(error)
        })
    })
  }

  // ログイン状態確認関数
  async function checkAuthState() {
    return await new Promise<void>((resolve, reject) => {
      // client only
      if (process.server) return resolve()
      const auth = getAuth()
      onAuthStateChanged(
        auth,
        (user) => {
          if (user) {
            user
              .getIdToken()
              .then((idtoken) => {
                token.value = idtoken
                resolve()
              })
              .catch(reject)
          } else {
            token.value = null
            resolve()
          }
        },
        (error) => {
          reject(error)
        }
      )
    })
  }

  // google認証関数
  async function loginWithGoogle() {
    const auth = getAuth();
    const provider = new GoogleAuthProvider();
    signInWithPopup(auth, provider)
      .then((result) => {
        const credential = GoogleAuthProvider.credentialFromResult(result);
        const token = credential?.accessToken;
        const user = result.user;
        console.log({ credential, token, user });
    })
    .catch((error) => {
        const errorCode = error.code;
        const errorMessage = error.message;
        const email = error.email;
        const credential = GoogleAuthProvider.credentialFromError(error);
        console.log({ errorCode, errorMessage, email, credential });
    });
  };

  // twitter認証関数
  async function loginWithTwitter() {
    const auth = getAuth();
    const provider = new TwitterAuthProvider();
    signInWithPopup(auth, provider)
      .then((result) => {
        const credential = TwitterAuthProvider.credentialFromResult(result);
        const token = credential?.accessToken;
        const user = result.user;
        console.log({ credential, token, user });
    })
    .catch((error) => {
        const errorCode = error.code;
        const errorMessage = error.message;
        const email = error.email;
        const credential = TwitterAuthProvider.credentialFromError(error);
        console.log({ errorCode, errorMessage, email, credential });
    });
  };



  return {
    signUp,
    signIn,
    signOut,
    token,
    checkAuthState,
    loginWithGoogle,
    loginWithTwitter
  }
}

ログイン状態の永続性

■ middleware/auth.ts

export default defineNuxtRouteMiddleware(async () => {
  if (!process.server) {
    const { checkAuthState, token } = useAuth()
    await checkAuthState()
    if (!token.value) {
      return navigateTo('/login', { replace: true })
    }
  }
})

Page コンポーネントや Layout コンポーネントにて、使用する middleware を指定すると、コンポーネントが作成 (setup) される前に(ページ遷移に先立ち) middleware 内の指定のファイルが実行されます。

<script setup>
definePageMeta({
  middleware: ['auth']
})
</script>

参考

Nuxt3 & Firebase9でFirebase Authentication決定版

Nuxt3のruntimeConfigで環境変数を設定する | デバッグライフ

【Vue.js】Nuxt3でComposables directoryを使ってグローバルstateを管理する

Nuxt 3 の Route Middleware で簡単な認証フローを構築する