🔖

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

2021/10/06に公開

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

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の上部で読み込ませます。

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