🔖

vite + firebase auth + pinia で認証フロー周りを作ってみた。

4 min read

なぜ作ったのかと言われると困るのですが、firebaseがv9が出たってことと、vuex5までの繋ぎでpiniaを触っておいてみたかったというのが理由です。今回はその時作成した、認証周りについての備忘録です。

サンプルリポジトリがありますので何かわからないことがあればこちらをご覧ください。

https://github.com/tsuki-lab/vue3-coding-style

デモサイト: https://vue3-coding-style.web.app/

piniaとは

https://pinia.esm.dev/

vuex5のrfcにより近い形の状態管理のライブラリです。
従来のvuexとは違い、mutationがなくなっていたり、よりtypesafeであったりします。
ReactHooksのuseContextに使い方としては近いのかな?って印象を個人的には持っています。

Vuex 5 RFC.
https://github.com/vuejs/rfcs/discussions/270

今回はこのpiniaにfirebase authを載せる形で認証フローを作成していけたらと思います。

firebaseの初期化

src/lib/firebase/index.ts
import {initializeApp} from 'firebase/app';

const firebaseConfig = {
  apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
  authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
  projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
  storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
  appId: import.meta.env.VITE_FIREBASE_APP_ID,
};

// Initialize Firebase
initializeApp(firebaseConfig);

viteがサポートする環境変数にfirebaseの各値を設定・初期化します。

https://ja.vitejs.dev/guide/env-and-mode.html

初期化するためのtsファイルはentryファイルである、main.tsの上部で読み込ませます。

上部で読み込まないと、初期化前にfirebase関連の処理が実行されてしまい、エラーになります。

src/main.ts
  import 'sakura.css';
  import '@/styles/global.scss';
+ import './lib/firebase';
  import {createPinia} from 'pinia';
  import {createApp} from 'vue';
  import App from './App.vue';
  import {router} from './lib/router';


  createApp(App)
    .use(router)
    .use(createPinia())
    .mount('#app');

authStoreの作成

src/lib/store/auth.store.ts
import {defineStore} from 'pinia';
import {useErrorStore} from './error.store';
import {
  getAuth,
  GoogleAuthProvider,
  signInWithPopup,
  onAuthStateChanged,
  signOut,
  User,
} from 'firebase/auth';

const auth = getAuth();
const provider = new GoogleAuthProvider();

export const useAuthStore = defineStore('auth', {
  state: () => ({
    currentUser: null as User | null,
  }),
  getters: {
    isLoggedIn: (state) => state.currentUser !== null,
  },
  actions: {
    async signIn() {
      try {
        const res = await signInWithPopup(auth, provider);

        this.$patch({currentUser: res.user});
      } catch (err) {
        if (err instanceof Error) {
          const errorStore = useErrorStore();
          errorStore.setError(err);
          return;
        }
        throw err;
      }
    },
    async signOut() {
      try {
        await signOut(auth);
        this.$reset();
      } catch (err) {
        if (err instanceof Error) {
          const errorStore = useErrorStore();
          errorStore.setError(err);
          return;
        }
        throw err;
      }
    },
    async getAuthState() {
      return new Promise((resolve, reject) => {
        onAuthStateChanged(auth,
            (user) => {
              this.$patch({currentUser: user});
              resolve(user);
            },
            (error) => {
              const errorStore = useErrorStore();
              errorStore.setError(error);
              reject(error);
            });
      });
    },
  },
});

前述した通り、piniaにはvuex4以前にあったmutationの概念はありません。
actionsでstateを更新します。

ですので、defineStoreの内部にはstate・getters・actionsの三種類のみとなります。

呼び出し側

src/pages/signIn.vue
<script lang="ts" setup>
import {routeNames} from '@/lib/router/routes';
import {useAuthStore} from '@/lib/store/auth.store';
import {useRouter} from 'vue-router';

const authStore = useAuthStore();
const router = useRouter();

const signIn = async () => {
  try {
    await authStore.signIn();
    router.push({name: routeNames.About});
  } catch (err) {
    console.error(err);
  }
};
</script>

<template>
  <h1>SignIn</h1>
  <button @click="signIn">Sign In</button>
</template>

最後に

久しぶりにvueを触ってみましたが、reactもいいけどvueも悪くないな〜って思いました。

Discussion

ログインするとコメントできます