この記事について
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
結論
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',
},
},
};
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プロジェクトが未作成の場合は、こちらを参考に作成して下さい。
apiKey
~ measurementId
には作成したプロジェクトから取得したものをそれぞれ指定してください。
export default {
modules: [
'@nuxtjs/firebase',
],
firebase: {
config: {
apiKey: 'yourApiKey',
authDomain: 'yourAuthDomain',
projectId: 'yourProjectId',
storageBucket: 'yourStorageBucket',
messagingSenderId: 'yourMessagingSenderId',
appId: 'yourAppId',
measurementId: 'yourMeasurementId',
},
services: {
auth: true,
},
},
};
<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内に実装しております。
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);
},
},
);
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
アクションを呼び出すための記述となります。
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
を表示
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
を型追加しておきます。
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
のファイルはサーバ起動時に自動で作成されます。
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度だけ呼び出される処理です。)
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を取得することができます。
-
nuxtServerInit
が実行される。 -
ログイン状態であれば、
nuxtServerInit
内の以下の記述によってログインユーザの情報を取得する。const authUser = res.locals.user; await dispatch('firebaseAuth/onAuthStateChangedAction', { authUser, });
-
firebaseAuth
storeのonAuthStateChangedAction
が呼び出されてuseId
stateにログインユーザのユーザIDがセットされる。 -
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のログインユーザの情報が取得できなくなりました。こちらの対処法などご存じの方いましたらご教示お願いします。)