はじめに
こんにちは。久しぶりのQiitaですが、UIとか変わってておーって感じです。せっかくのクリスマスなので何か書いてみようと書いたのが、「Electron + nuxt.js + Firebase v9 でFirebase Authenticationのログイン状態をVuexに保存したい。」ということで、要素盛りすぎだろ!な感じでいきたいと思います。というのも、ここ半年は文化祭の準備で色々忙しく、ゆっくり考えて開発する時間もなかったわけで、「ここはいっちょでっかいProjectやってみるか!」ということで「TecTask」なるタスク管理ソフトの開発を画策しています。公開してサービスとして展開することは全く考えておらず、自分用として企業並みの機能を兼ね備えたものを作ろうとしている次第です。文化祭についてやったことはおいおい書いていくとしてGithubやGitを使ったり、Nuxt.jsでの開発の経験をぶつけようというわけです。
今回はNode.jsでアプリが開発できちゃうElectron
をNuxt.js
と併用した環境でFirebase Authentication
を使いたいなという感じです。Electron
がFirebase
に関係してくるところは少ないので複数の記事をごっちゃにした感じになってしまいました。以下はこの記事でやっていくことです。
-
Electron + nuxt.js
環境を作りたいよ -
Firebase SDK v9
をnuxtで使用したいよ -
Firebase Authentication
をnuxtで使用したいよ - ログインの状態を
vuex
で保存したいよ
第1章 導入
ここではElectronとNuxtの導入、そしてFirebaseSDKv9の導入をおこなっていきます。
プロジェクトの生成
npm install -g vue-cli
vue init michalzaq12/electron-nuxt <project-name>
cd <project-name>
yarn install
yarn dev
ここでは、michalzaq12
さんのelectron-nuxt
をつかって手っ取り早く環境構築をしていきます。(Github)
nuxt
のルートフォルダはsrc
内のrenderer
になります。
必要なプラグインの導入
今回はfirebase
とAPIキーを保存するためにdotenv
を導入します。
yarn add firebase
yarn add @nuxtjs/dotenv
僕の環境でのバージョンは以下のとおりです。
"@nuxtjs/dotenv": "^1.4.1",
"firebase": "^9.6.1"
ここで注意してもらいたいのがfirebaseSDK
のバージョンがv9
だと言うことです。v8
からv9
で大きな変更が加えられています。一応、互換性はありますが、v9
の解説も兼ねているのでv9
で書いていきます。
firebase init
最後にこのコマンドを実行することでfirestore
などの設定ファイルをローカルに持ってきます。これが果たして必要かどうかは未検証です。このコマンドの実行前にFirebase Consoleでプロジェクトを生成しとくことをお勧めします。
なお今後、Electron
の環境というより、nuxt.js
の環境となるので、これでこの記事でElectron
が出てくることは無くなります。ただ、Electron
特有の要素があるかもしれないので詳しい方はぜひコメントください。
firebaseの初期化
firebase
はAPIキーなどを用いた初期化が最初に必要になります。それをplugin
でおこなっていきます。なお、あらかじめ使用するFirebaseプロジェクトを生成し、設定 > マイアプリ
からアプリ(今回ならWEBアプリ)を生成し、生成後の画面のSDK の設定と構成
からapiKey, authDomain, databaseURL, projectId, storageBucket, messagingSenderId, appId, measurementId
を取得してください。(いつでも設定からみれるのでメモする必要はない。)
直下に.env
という名前のファイルを作りAPIキー周りを保存します。
API_KEY=
AUTH_DOMAIN=
DATABASE_URL=
PROJECT_ID=
STORAGE_BUCKET=
MESSAGING_SENDER_ID=
APP_ID=
MEASUREMENT_ID=
また、nuxt.config.js
にdotenv
を使うことを明記しておきます。
modules: ["@nuxtjs/dotenv"],
つぎに、plugins
フォルダにfirebase.js
を生成して以下を書き込みます。
import { initializeApp } from "firebase/app";
import { initializeAuth } from "firebase/auth";
const firebaseConfig = {
apiKey: process.env.API_KEY,
authDomain: process.env.AUTH_DOMAIN,
databaseURL: process.env.DATABASE_URL,
projectId: process.env.PROJECT_ID,
storageBucket: process.env.STORAGE_BUCKET,
messagingSenderId: process.env.MESSAGING_SENDER_ID,
appId: process.env.APP_ID,
measurementId: process.env.MEASUREMENT_ID,
};
const app = initializeApp(firebaseConfig);
// Initialize Auth
export const auth = initializeAuth(app);
これでFirebaseはInitialize出来ました。なお、最新のnuxt
ではdotenv
の代わりにnuxt
標準のRuntimeConfig
機能で同じことができます。が、electron
のせいなのかなんなのか僕の環境では.env
を認識してくれなかったのでdotenv
をやむなく使いました。なにかわかる方いらっしゃいますでしょうか。
RuntimeConfig
についてはこの記事がわかりやすいです。
最後にplugin
を使うことを明記してここは完成です。
module.exports = {
// 中略...
plugins: ["@/plugins/firebase.js"]
}
第2章 実装
ログイン処理を書く
Vuex
にログイン処理を書いていきます。
store
フォルダ内にauth.js
を生成して以下を書き込みます。
import { auth } from "@/plugins/firebase.js";
import { onAuthStateChanged, signInWithEmailAndPassword, signOut } from "firebase/auth";
export const state = () => ({
isLoggedIn: false, // ログイン状態
user: {}, // ログイン情報
});
export const mutations = {};
// stateの内容を直接とるのではなくgetterを通して取得する方が良い。
export const getters = {
isAuthenticated: (state) => !!state.isLoggedIn, // ログイン状態
currentUserInfo: (state) => state.user, // ログイン情報
};
export const actions = {
firebaseAuthLogin({ state }, loginInfo) {
if (!loginInfo.loginMailAddress || !loginInfo.loginPassword) return null;
signInWithEmailAndPassword(
auth,
loginInfo.loginMailAddress,
loginInfo.loginPassword
)
.then((userCredential) => {
const user = userCredential.user;
// ログイン成功
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
// ログイン失敗
});
},
firebaseAuthLogout({ state }) {
signOut(auth)
.then(() => {
state.isLoggedIn = false;
// ログアウト成功
})
.catch((error) => {
// ログアウト失敗
});
},
setAuthChangedListener({ commit }) {
onAuthStateChanged(auth, (user) => {
user = user ? user : {};
state.user = user;
state.isLoggedIn = user.uid ? true : false;
});
},
};
今回はメール/パスワードでログインすることを想定しました。Firebase Authentication
のSign-in method
で色々なプロバイダ(例えばGoogleやFacebookなど)を利用することができます。その際、firebaseAuthLogin
を改変する必要があるのでご注意ください(詳しくはこちら)。
onAuthStateChanged
でログイン情報の変化に対応しています。ログインしているか否かはisLoggedIn
に格納され、 isAuthenticated
を介して取得可能です。firebaseAuthLogin
でログイン、firebaseAuthLogout
でログアウトできます。度々出てくる{ state }
は自動で与えられるもので、vuex内のstateを参照できます。
また、Vuex内の関数は第二引数までしか渡すことができません。(第一引数は{ state }
)そこで、引数をオブジェクト型にし、階層をもって三つ以上の引数を渡しています(詳しくはこちら)。
ではどのように使用するのか見てみましょう。
middlewareをつくる
middlewareはページ遷移時に比較的早く呼ばれる部分でページに進入できるかどうかを振り分けます。
middleware
ディレクトリ内にcheckAuth.js
を生成し、以下のとおり記述します。
const allowUnauthorizedPages = ["index", "login"];
const banAuthorizedPages = ["login"];
export default function ({ store, route, redirect }) {
store.dispatch("auth/setAuthChangedListener");
if (
!store.getters["auth/isAuthenticated"] &&
!allowUnauthorizedPages.includes(route.name)
) {
// 未ログインかつ、未ログインで入ってはいけないページの場合はログインページへ
redirect("/login");
}
if (
store.getters["auth/isAuthenticated"] &&
banAuthorizedPages.includes(route.name)
) {
// ログイン済みかつ、ログイン済みで入ってはいけないページの場合はホームへ
redirect("/");
}
}
allowUnauthorizedPages
は未ログインで入れるページを記録します。このとき、route.name
と比較するのでホームはindex
となることに注意してください。また、banAuthorizedPages
ではログイン済みでは入れないページ(例えばログインページなど)を記述してください。それぞれ条件が揃った時にredirect
される仕組みです。
store.dispatch()
でstore/auth.js
のsetAuthChangedListener
を呼び、ログイン情報に変化があったときのコールバックを登録しときます。なお、auth/setAuthChangedListener
のようにファイル名/Action名
を引数に取ります。store.getters
も同様にとります。
最後にこのmiddleware
を全ページに登録して終わりです。
router: {
middleware: ["checkAuth"],
},
長期的なテストをおこなっていないため、isAuthenticated
はfalseだが、firebase上では認証されていると言う可能性もありますが、ユーザ視点ではセッションが切れた程度にしか感じないためその場合でもログインページに飛ばしています。そもそもそのような状況があるかどうかもわからないのでもし深刻であればfirebase/auth
に現在のユーザ情報をとる関数があるはずなのでそれを使うといいと思います。
第3章 使い方
ここまでpage本体には手を出してこなかったため、かなりきれいな記述ができ他と思います。ですが、どうしてもpageで書かないといけないことがあります。それはログイン状態やユーザ情報の取得とログインです。
vuexからログイン状態やログイン情報を取得する
<template>
<!-- 中略 -->
<div>{{ isAuthenticated }}:{{ currentUserInfo }}</div>
<!-- 中略 -->
</template>
<script>
// 中略...
import { mapGetters } from "vuex";
export default {
// 中略...
computed: {
...mapGetters("auth", ["isAuthenticated", "currentUserInfo"]),
},
// 中略...
}
</script>
mapGetters
で<template>
内にあるように普通の変数として扱えるようになります。
vuexのactionsを叩いてログインする
<template>
<!-- 中略 -->
<div @click="login">ログイン</div>
<!-- 中略 -->
</template>
<script>
// 中略...
import { mapActions } from "vuex";
export default {
// 中略...
data() {
return {
loginMailAddress: "",
loginPassword: "",
};
},
methods: {
...mapActions("auth", ["firebaseAuthLogin"]),
login() {
this.firebaseAuthLogin({
loginMailAddress: this.loginMailAddress,
loginPassword: this.loginPassword,
});
},
},
// 中略...
}
</script>
mapActions
で指定したactions
をこちらのmethods
として扱えるようになります。先述したとおり、メールアドレスとパスワードは第三引数を取れないため、Object型で渡していることがわかると思います。v-model
などでthis.loginMailAddress
やthis.loginMailAddress
をどこかの<input>
と紐づければログイン処理は完成です。なお、null
除外処理はしっかり書いた方がいいと思いますが今回は省略しました。
さいごに
今回はかなり急ぎ&雑に書いてしまったのでこのコードが完璧に動くかどうかは未検証です。もしかしたら、書いていない部分で弄り回しててそこが効いてる可能性もありますが...。その場合はご指摘ください。また、electron
がいるとRuntimeConfig
使えない(?)問題については鋭意調査中ですが何か知っている方がいればコメントお願いします。
今回で一番戸惑ったのはfirebase v9
の圧倒的情報不足です...。公式リファレンスを読みまくって実装したのでもしかしたらもっといい実装方法があったかもしれません。それはおいおいみなさんが記事を出してくれることでしょう。そう祈って Merry Christmas –––良いお年を。