LoginSignup
3
3

More than 1 year has passed since last update.

【Nuxt.js】SSRモードでFirebase Authを使用する

Last updated at Posted at 2022-05-28

この記事について

Nuxt.jsのSSR使用時に、Firebase Authを使用してログイン制御を行ってみましたので、実装方法をメモとして残したいと思います。
Nuxt.jsのMiddleware(SSR中の処理)で、Firebaseでログイン済みである/未ログインであるの判定を行う方法を記載したいと思います。
主に Firebase Auth with SSR - Nuxt Firebase を参考に実装しております。
また、Firebase AuthについてはGoogle認証で実装しております。

本記事内でstoreやFirebase Authの基本的な実装を記載しておりますが、細かな説明までは行っておりません。
storeやFirebase Authの基本的な実装に関しては、以下を参照ください。
Vuex ストア - Nuxt.js
JavaScript で Google を使用して認証する - Firebase

対象読者

  • Nuxt.jsのSSRモードでFirebase Authの実装を行いたい方
  • Vue.jsのstoreの実装経験のある方

環境

内容 バージョン
node 16.14.2
yarn 1.22.15
nuxt 2.15.8
nuxt-typed-vuex 0.3.0
@nuxtjs/firebase 8.2.2
@nuxtjs/pwa 3.3.5
firebase-admin 10.1.0

最終的に作成されるディレクトリ構成

sampleProject
├middleware
| └router.ts
├pages
| ├index.vue
| ├login.vue
| └logout.vue
├static
| └firebase-auth-sw.js
├store
| ├firebaseAuth.ts
| └index.ts
├types
| └index.d.ts
└nuxt.config.js

結論

nuxt.config.js
export default {
  modules: [
    '@nuxtjs/pwa',
    '@nuxtjs/firebase',
  ],
  firebase: {
    config: {
      apiKey: 'yourApiKey',
      authDomain: 'yourAuthDomain',
      projectId: 'yourProjectId',
      storageBucket: 'yourStorageBucket',
      messagingSenderId: 'yourMessagingSenderId',
      appId: 'yourAppId',
      measurementId: 'yourMeasurementId',
    },
    services: {
      auth: {
        ssr: true,
        initialize: {
          onAuthStateChangedAction:
            'firebaseAuth/onAuthStateChangedAction',
        },
      },
    },
  },
  pwa: {
    meta: false,
    icon: false,
    workbox: {
      importScripts: ['./firebase-auth-sw.js'],
      dev: process.env.NODE_ENV === 'development',
    },
  },
};
store/index.ts
import {
  getAccessorType,
  getterTree,
  mutationTree,
  actionTree,
} from 'typed-vuex';
import * as firebaseAuth from './firebaseAuth';

export const state = () => ({});
export const getters = getterTree(state, {});
export const mutations = mutationTree(state, {});
export const actions = actionTree(
  { state, getters, mutations },
  {
    async nuxtServerInit({ dispatch }, { res }) {
      if (res && res.locals && res.locals.user) {
        const authUser = res.locals.user;
        await dispatch('firebaseAuth/onAuthStateChangedAction', {
          authUser,
        });
      }
    },
  },
);
export const accessorType = getAccessorType({
  state,
  getters,
  mutations,
  actions,
  modules: {
    firebaseAuth,
  },
});

Nuxt.jsでFirebase Authを使用して単純なログイン・ログアウト処理を実装してみる

まずは、シンプルにFirebase AuthのGoogle認証を使用して、ログイン・ログアウトを実装します。

まずは必要なライブラリのインストール

yarn add @nuxtjs/firebase

Firebase Authを使用できるように nuxt.config.js に以下を追加
「Firebaseプロジェクトは作成済み」かつ「Google認証のAuthenticationは有効化済み」とします。
Firebaseプロジェクトが未作成の場合は、こちらを参考に作成して下さい。
apiKeymeasurementId には作成したプロジェクトから取得したものをそれぞれ指定してください。

nuxt.config.js
export default {
  modules: [
    '@nuxtjs/firebase',
  ],
  firebase: {
    config: {
      apiKey: 'yourApiKey',
      authDomain: 'yourAuthDomain',
      projectId: 'yourProjectId',
      storageBucket: 'yourStorageBucket',
      messagingSenderId: 'yourMessagingSenderId',
      appId: 'yourAppId',
      measurementId: 'yourMeasurementId',
    },
    services: {
      auth: true,
    },
  },
};
pages/index.vue
<template>
  <div>
    <button @click="onClickedLoginButton">ログイン</button>
    <button @click="onClickedLogoutButton">ログアウト</button>
  </div>
</template>

<script lang="ts">
import Vue from 'vue';

export default Vue.extend({
  methods: {
    async onClickedLoginButton() {
      const provider = new this.$fireModule.auth.GoogleAuthProvider();
      await this.$fire.auth
        .signInWithPopup(provider)
        .then((userResult) => console.log(userResult))
        .catch((error) => console.error(error));
    },
    async onClickedLogoutButton() {
      await this.$fire.auth.signOut();
    },
  },
});
</script>

これで画面上でGoogleアカウントを使用してログイン・ログアウトが可能となります。
しかし、通常は複数ページにまたがってログイン状態を共有したいと思いますので、ログインユーザのユーザIDをstoreで管理して「storeにログインユーザのユーザIDが存在する場合はログイン済み」と判定できるように実装したいと思います。

storeでログインユーザのユーザIDを管理する

Firebase AuthでログインしたユーザのユーザIDを管理するstoreを作成します。
Firebase Auth用の個別storeを管理する firebaseAuth.ts と 他のstoreも含めて管理する index.ts に分けております。
また、onAuthStateChangedActionを使用して画面初期表示時やログイン状態変更時のイベントキャッチが可能であるため、 onAuthStateChangedAction をstore内に実装しております。

store/firebaseAuth.ts
import { getterTree, mutationTree, actionTree } from 'typed-vuex';

export const state = (): firebaseUserInfo => ({
  userId: null as string | null,
});
export type RootState = ReturnType<typeof state>;

export const getters = getterTree(state, {
  getUserId(state): string {
    return state.userId;
  },
});

export const mutations = mutationTree(state, {
  setUserId(state, userId: string) {
    state.userId = userId;
  },
});

export const actions = actionTree(
  { state, mutations },
  {
    async onAuthStateChangedAction({ commit }, { authUser }) {
      if (authUser === null) {
        commit('setUserId', null);
        return;
      }

      commit('setUserId', authUser.uid);
    },
  },
);
store/index.ts
import {
  getAccessorType,
  getterTree,
  mutationTree,
  actionTree,
} from 'typed-vuex';
// その他のstoreがある場合はここで他のstoreをimportする
import * as firebaseAuth from './firebaseAuth';

export const state = () => ({});
export const getters = getterTree(state, {});
export const mutations = mutationTree(state, {});
export const actions = actionTree({ state, getters, mutations }, {});
export const accessorType = getAccessorType({
  state,
  getters,
  mutations,
  actions,
  modules: {
    // その他のstoreがある場合はmodulesに他のstoreを追加する
    firebaseAuth,
  },
});

onAuthStateChangedAction でログイン状態変更時のイベントキャッチを可能とするために、 nuxt.config.js に記述を追加します。
onAuthStateChangedAction 起動時に firebaseAuth storeの onAuthStateChangedAction アクションを呼び出すための記述となります。

nuxt.config.js
export default {
  modules: [
    '@nuxtjs/firebase',
  ],
  firebase: {
    config: {
      apiKey: 'yourApiKey',
      authDomain: 'yourAuthDomain',
      projectId: 'yourProjectId',
      storageBucket: 'yourStorageBucket',
      messagingSenderId: 'yourMessagingSenderId',
      appId: 'yourAppId',
      measurementId: 'yourMeasurementId',
    },
    services: {
      auth: {
        // 以下追加
        initialize: {
          onAuthStateChangedAction:
            'firebaseAuth/onAuthStateChangedAction',
        },
      },
    },
  },
};

これで、 pages/index.ts の画面からログインするとstoreにユーザIDがセットされ、ログアウトするとstoreのユーザIDにnullがセットされるようになります。

Firebase Auth & store のみの場合の課題

ここで、以下のような要件があった場合、 middleware/router.ts を作成して実装を試みてみます。

  • ログイン時...pages/login.vue を表示
  • 未ログイン時...pages/logout.vue を表示
middleware/router.ts
import { Context, Middleware } from '@nuxt/types';

const middlware: Middleware = ({ redirect, $accessor, $fire }: Context) => {
  // 動作確認用で、storeと$fireの両方のユーザIDをlogに出力してみる
  const firebaseLoginUserId = $accessor.firebaseAuth.getUserId;
  console.log(firebaseLoginUserId);
  console.log($fire.auth.currentUser?.uid);
  if (firebaseLoginUserId === null) {
    // ログインしていない場合はpages/logout.vueへ遷移
    redirect('/logout');
  } else {
    // ログインしている場合はpages/login.vueへ遷移
    redirect('/login');
  }
};

export default middlware;

middlewareの処理内でstoreにアクセスできるように、 $accessor を型追加しておきます。

types/index.d.ts
import { accessorType } from '~~/store';

declare module '@nuxt/types' {
  interface NuxtAppOptions {
    $accessor: typeof accessorType;
  }
  interface Context {
    $accessor: typeof accessorType;
  }
}

ここで、Firebaseでログイン後にブラウザの画面リロードしてみます。
すると、ログインしているにもかかわらず console.log(firebaseLoginUserId);console.log($fire.auth.currentUser?.uid); の両方で null となってしまいます。
これは以下が原因によるものです。

  • storeに入れたデータは、画面リフレッシュ時にはリセットされてしまう
  • FirebaseはデフォルトではSSR側の処理内でログイン状態を取得できない
  • onAuthStateChangedAction が実行されるのは画面表示後であるため、SSR時のmiddlewareではまだ実行されず、storeのユーザIDはnullとなる

Firebase Auth, PWA, nuxtServerInit & store(課題の解決)

そこで、こちらのリファレンスを見ると、SSRでFirebaseでログインしたユーザのログイン状態を取得できるようなので、これに従って実装していきたいと思います。

必要なライブラリのインストール
内部的にサーバサイドでのFirebase Authを使用するため、firebase-admin をインストールします。

yarn add firebase-admin @nuxtjs/pwa

また、Firebase AuthのSSRを有効化して、インストールした @nuxtjs/pwa を適用するため nuxt.config.js を以下のように書き換えます。
importScripts に記載した firebase-auth-sw.js のファイルはサーバ起動時に自動で作成されます。

nuxt.config.js
export default {
  modules: [
    '@nuxtjs/pwa', // 追加
    '@nuxtjs/firebase',
  ],
  firebase: {
    config: {
      apiKey: 'yourApiKey',
      authDomain: 'yourAuthDomain',
      projectId: 'yourProjectId',
      storageBucket: 'yourStorageBucket',
      messagingSenderId: 'yourMessagingSenderId',
      appId: 'yourAppId',
      measurementId: 'yourMeasurementId',
    },
    services: {
      auth: {
        ssr: true, // 追加
        initialize: {
          onAuthStateChangedAction:
            'firebaseAuth/onAuthStateChangedAction',
        },
      },
    },
  },
  // 以下追加
  pwa: {
    meta: false,
    icon: false,
    workbox: {
      importScripts: ['./firebase-auth-sw.js'],
      dev: process.env.NODE_ENV === 'development',
    },
  },
};

そして、Nuxt.js使用時に一番最初に実行される nuxtServerInit でFirebaseのログインユーザの情報を取得することができます。
nuxtServerInitの使い方についてはこちらを参照してください。
nuxtServerInitは store/index.ts にのみ記載可能です。
(画面ロード時に1度だけ呼び出される処理です。)

store/index.ts
import {
  getAccessorType,
  getterTree,
  mutationTree,
  actionTree,
} from 'typed-vuex';
import * as firebaseAuth from './firebaseAuth';

export const state = () => ({});
export const getters = getterTree(state, {});
export const mutations = mutationTree(state, {});
export const actions = actionTree(
  { state, getters, mutations },
  // nuxtServerInitを追加
  {
    async nuxtServerInit({ dispatch }, { res }) {
      if (res && res.locals && res.locals.user) {
        const authUser = res.locals.user;
        await dispatch('firebaseAuth/onAuthStateChangedAction', {
          authUser,
        });
      }
    },
  },
);
export const accessorType = getAccessorType({
  state,
  getters,
  mutations,
  actions,
  modules: {
    firebaseAuth,
  },
});

こうすることで、以下の流れで処理が実行されてmiddlewareでログインユーザのユーザIDを取得することができます。

  1. nuxtServerInit が実行される。

  2. ログイン状態であれば、nuxtServerInit 内の以下の記述によってログインユーザの情報を取得する。

    const authUser = res.locals.user;
    await dispatch('firebaseAuth/onAuthStateChangedAction', {
      authUser,
    });
    
  3. firebaseAuth storeの onAuthStateChangedAction が呼び出されて useId stateにログインユーザのユーザIDがセットされる。

  4. middlwareの処理が実行されてmiddleware内で $accessor.firebaseAuth.getUserId によってstoreからユーザIDを取得することが可能(ログイン後に画面リロードを行うと pages/login.vue が表示される)。

また、今回はmiddleware内でのユーザID取得を試みましたが、asyncDataやfetchなどSSRにかかわるすべての処理の中でも同様にログインユーザの情報が取得できるようになります。
(Firebase AuthのSSRの設定などを行わない場合は、asyncDataやfetchでもmiddlware同様にログインユーザの乗法が取得できなくなります。)
(また今回、Nginxを使用して test.localhost のようなサブドメインを使用した際にもSSR時にFirebaseのログインユーザの情報が取得できなくなりました。こちらの対処法などご存じの方いましたらご教示お願いします。)

参考

Firebase Auth with SSR - Nuxt Firebase

3
3
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
3
3