最近、個人でFirebaseをバックエンドとしたNuxtによるWebアプリケーションの開発をしています。その中で、Firebaseの機能の1つであるFirebase Authenticationを使用してユーザーの認証機能を実装してみました。この記事では、NuxtアプリでFirebase Authenticationによるユーザー認証機能を実装する方法について書いてみたいと思います。
ここで検討するアプリケーションは、次の仕様を前提としています。
ここでは詳細は割愛しますが、Firebaseにアカウントを作成し、プロジェクトを作成する必要があります。
プロジェクトを作成すると、
が払い出されます。 これらの値はNuxtアプリケーションからFirebaseへ接続する際に使用します。
Nuxtアプリケーションのベースは、vue-cliを使用して作成します。
vue-cliがインストールされていない場合は、インストールします。
次のコマンドで、アプリケーションのベースを作成し、依存するnpmパッケージ群をインストールします。
$ vue init nuxt-community/starter-template <project-name>
$ cd <project-name>
$ npm install
そして、
$ npm run dev
でアプリケーションを起動し、正常に動作することを確認します。
アプリケーションからFirebaseを利用するために、firebaseパッケージをインストールします。
$ npm install firebase --save
NuxtアプリケーションからFirebaseに接続するためのモジュールを作成します。
plugins
ディレクトリにfirebase.js
というファイルを作成し、次のような実装とします。
import firebase from 'firebase'
if (!firebase.apps.length) {
firebase.initializeApp({
apiKey: <APIKEY>,
authDomain: <AUTHDOMAIN>,
databaseURL: <DATABASEURL>,
projectId: <PROJECTID>,
storageBucket: <STORAGEBUCKET>,
messagingSenderId: <MESSAGINGSENDERID>
})
}
export default firebase
ここで、Firebaseの各種情報を管理するのに、direnvを利用すると便利です。 direnvの導入方法は割愛しますが、direnvを使用する場合は、プロジェクトに対して、
という変更を行います。
次の内容を.envrcに書きます。
export APIKEY=<APIKEY>
export AUTHDOMAIN=<AUTHDOMAIN>
export DATABASEURL=<DATABASEURL>
export PROJECTID=<PROJECTID>
export STORAGEBUCKET=<STORAGEBUCKET>
export MESSAGINGSENDERID=<MESSAGINGSENDERID>
<>
の中身は、Firebaseプロジェクトで払い出された値を指定します。
const webpack = require('webpack')
module.exports = {
// ...
build: {
extend (config, { isDev, isClient }) {
// ...
config.plugins.push(
new webpack.EnvironmentPlugin([
'APIKEY',
'AUTHDOMAIN',
'DATABASEURL',
'PROJECTID',
'STORAGEBUCKET',
'MESSAGINGSENDERID'
])
)
}
}
}
import firebase from 'firebase'
if (!firebase.apps.length) {
firebase.initializeApp({
apiKey: process.env.APIKEY,
authDomain: process.env.AUTHDOMAIN,
databaseURL: process.env.DATABASEURL,
projectId: process.env.PROJECTID,
storageBucket: process.env.STORAGEBUCKET,
messagingSenderId: process.env.MESSAGINGSENDERID
})
}
export default firebase
アプリケーション全体で、ユーザーの認証状態を管理するために、Vuexによる状態管理を実装します。
stateとして持っておきたいのが、認証されたユーザー情報です。
したがって、次のように実装します。
export const state = () => ({
user: null
})
getters
、actions
、mutations
は、以降必要応じて随時足していきます。
具体的な認証機能は後で実装することにして、まずはページコンポーネントを定義してみます。
ファイルは、
として、pages
ディレクトリに格納します。
今回は、テンプレートエンジンにpugを、cssメタ言語にscssを使用することにして、 次のパッケージ群をインストールします。
$ npm install --save-dev pug pug-loader node-sass sass-loader
この段階では、それぞれ、次の実装としておきます。
<template lang="pug">
.container
h1 index
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
</style>
<template lang="pug">
.container
h1 login
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
</style>
次に、冒頭で書いた、
を実現する方法を考えてみます。これらは、ページ遷移の際にチェックするのが良さそうです。幸い、Nuxt.jsは、 Middlewareという機構を提供しており、
ミドルウェアを使って、あるページまたはあるページのグループがレンダリングされる前に実行される関数を定義することができます。
とあるので、今回の仕様を実現するにはうってつけの機能です。
今回実装するMiddlewareでは、
というロジックが必要になるため、Storeに次のgetter
を追加しておきます。
export const getters = {
isAuthenticated (state) {
return !!state.user
}
}
上記のgetter
を使用して、次のMiddlewareを定義します。
(authenticated.js
というファイル名でmiddleware
ディレクトリに格納します。)
export default function ({ store, route, redirect }) {
if (!store.getters.isAuthenticated && route.name !== 'login') {
redirect('/login')
}
if (store.getters.isAuthenticated && route.name === 'login') {
redirect('/')
}
}
そして、nuxt.config.js
を編集し、作成したmiddlewareを登録します。
module.exports = {
// ...
router: {
middleware: 'authenticated'
}
}
この実装によって、ルートページにアクセスすると、(ログイン済みのユーザー情報がないため、)必ずログインページにリダイレクトされるようになります。
ここからは、いよいよFirebase Authenticationを使用して、ログイン機能を作っていきたいと思います。
Googleアカウントでの認証機能を実装するにあたり、次のAPIを使用します。
ログイン機能のフローは、次のようになります。
login
ページコンポーネントにログインボタンを配置し、クリックイベントハンドラーを実装していきます。
クリックイベントハンドラーでは、Vuexのactionを実行するようにします。ということで、storeモジュールに次のactionを追加します。
import firebase from '@/plugins/firebase'
const googleProvider = new firebase.auth.GoogleAuthProvider()
export const actions = {
login () {
return new Promise((resolve, reject) => {
firebase.auth().signInWithRedirect(googleProvider)
.then(() => resolve())
.catch((err) => reject(err))
})
}
}
login
ページコンポーネントにログインボタンを追加し、クリック時に上記のactionを実行するよう修正します。
<template lang="pug">
.container
h1 login
input(type='button'
value='Login in with Google'
@click='doLogin')
</template>
<script>
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions([
'login'
]),
doLogin () {
this.login()
.then(() => console.log('resloved'))
.catch((err) => console.log(err))
}
}
}
</script>
<style lang="scss" scoped>
</style>
この実装により、ログインボタンをクリックすると、Googleが提供する認証ページに遷移するようになり、 Googleアカウントの認証情報を入力すると、再びアプリケーション側にリダイレクトされます。
ここで、アプリケーション側にリダイレクトされた際に、上述のonAuthStateChanged
メソッドを実行することで、
認証済みのユーザー情報が取得できるようになり、取得したユーザー情報をStoreに格納することで、
認証済みのユーザーをアプリケーションで管理できます。
認証済みのユーザーをStoreに格納するために、次のmutationとactionを追加しておきます。
export const mutations = {
setUser (state, payload) {
state.user = payload
}
}
export const actions = {
// ...
setUser ({ commit }, payload) {
commit('setUser', payload)
}
}
onAuthStateChanged
を呼び出すにあたり、ちょっとした工夫が必要です。
Googleの認証ページからリダイレクトされるページは、アプリケーションのルートページ(/)になります。
ですが、リダイレクトされた時点では、まだ認証済みユーザー情報がStoreに格納できていないため、
先ほど追加したMiddleware(authenticated.js
)により、ログインページ(/login)にリダイレクトします。
そのため、onAuthStateChanged
を実行するのはlogin
ページコンポーネントとする必要があります。
また、login
ページコンポーネントでonAuthStateChanged
を実行し、コールバックが実行されるのを待つには、
コンポーネントのmounted
フックで非同期処理として実行すれば良さそうです。
(この方法は、https://github.com/potato4d/nuxt-firebase-sns-example)を参考にさせて頂きました。)
async mounted () {
let user = await new Promise((resolve, reject) => {
firebase.auth().onAuthStateChanged((user) => resolve(user))
})
this.setUser(user) // setUser is mapped action from vuex
if (user) {
this.$router.push('/') // if non-null user given, go to root page.
}
}
上記のようにすることで、login
ページコンポーネントがマウントされた時点で、
とすることができました。ですが、まだ少しだけ気持ち悪いことがおきます。
ユーザーが認証済みの状態でルートページにアクセスすると、
mounted
フックが動き、onAuthStateChanged
が実行される。となり、一瞬ログインページが表示されてしまいます。
これを対処するための方法としては、mounted
フックが完了するまでの間、
ローディング画面を表示する、という方法が考えられます。
ログアウト機能を実装するにあたり、次のAPIを使用します。
ルートページコンポーネントにログアウトボタンを配置し、クリックイベントハンドラーを実装していきます。 クリックイベントハンドラーでは、Vuexのactionを実行するようにします。次のactionを追加します。
export const actions = {
// ...
logout ({ commit }) {
return new Promise((resolve, reject) => {
firebase.auth().signOut()
.then(() => {
commit('setUser', null)
resolve()
})
})
}
// ...
}
ルートページコンポーネントにログアウトボタンを追加し、クリック時に上記のactionを実行するよう修正します。
<template lang="pug">
.container
h1 index
input(type='button'
value='Logout'
@click='doLogout')
</template>
<script>
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions([
'logout'
]),
doLogout () {
this.logout()
.then(() => {
this.$router.push('/login')
})
.catch((err) => console.log(err))
}
}
}
</script>
<style lang="scss" scoped>
</style>
これで、ログアウトボタンをクリックすることで、FirebaseのsignOut
処理を経由して、
再びログインページに遷移するようになります。
NuxtアプリでFirebase Authenticationによるユーザー認証機能を実装する方法について纏めてみました。
誰かの参考になれば何よりです...
コードは、shimar/nuxt-firebase-auth-exampleで公開しています。
Next.jsでも似たようなことをやってみました。 Next.js + Firebase Authentication