JavaScript
チュートリアル
Vue.js
Firebase
nuxt.js

FirebaseとNuxt.jsを使ってユーザ認証関係を簡単に作ってみる+1ヶ月前の自分に教えたいリンク集

この記事は、Nuxt.js #2 Advent Calendar 2018の12日目の記事です。


概要

この記事ではNuxt.jsでプロジェクトの作成からユーザ認証関係(ログインしないと見れないページの作成方法)を紹介します。

具体的には認証を行うコンポーネントやプラグインを作成し、認証まわりの処理をおこないます。コンポーネントで作成するため再利用性は高い(はず)です。ご利用ください。

※Firebase, Nuxt, Vue初心者が書いた記事です。もっといい書き方とかあれば教えてください!


前提

nodeは10.14.1, npmは6.4.1を使っていきます。

$ node -v

v10.14.1
$ npm -v
6.4.1


1. プロジェクトの作成

Nuxt.js x Firebase事始め - the industrialを見ると、Nuxt.jsのプロジェクトの作成からFirebaseへのデプロイができます。

とりあえず、この記事だけで完結できるように必要なコマンドを説明します。

まず, Nuxtのプロジェクトを作成します。

ここらへんは以下の公式のリファレンスと一緒です。

インストール - Nuxt.js

npx create-nuxt-app <project-name>

途中でいろいろ聞かれますが、基本エンター連打で大丈夫です。(お好きに選んでください)

cd <project-name>

npm run dev

をして、http://localhost:3000でページが見れます。


2. Firebaseのプラグインの作成

Firebaseをプロジェクトに追加します。package.jsonに追加したいので--saveオプションを忘れずに。

npm install firebase --save

次にプロジェクトのpluginsの中にfirebase.jsを作成し、以下のように自分のFirebaseのプロジェクトのキーを記入します。


~/plugins/firebase.js

import firebase from 'firebase'

if (!firebase.apps.length) {
firebase.initializeApp({
apiKey: "API_KEY",
authDomain: "APP_ID.firebaseapp.com",
databaseURL: "https://PROJECT_ID.firebaseio.com",
projectId: "PROJECT_ID",
storageBucket: "PROJECT_ID.appspot.com",
messagingSenderId: "1234567890"
})
}

export default firebase


キーの見方はFirebaseの自分のプロジェクトにいき、歯車アイコンをクリックすると下部に以下の項目があります。このウェブアプリにFirebaseを追加をクリックし、

スクリーンショット 2018-12-13 10.27.37.png

表示される以下のconfigを上記のコードのfirebase.initializeApp({...})に記入してください。

スクリーンショット 2018-12-13 10.29.39.png

これで自分のプロジェクトと紐付けができました。

これ以降このプロジェクトでFirebase関係のAPIを用いる時はimport firebase from '~/plugins/firebase'をして使います。するとキーが入ったfirebaseが使えるようになります。

Firebaseのキーをコードにそのまま書いてもいいのかという問題点については以下の記事をご覧ください。結論から言うとDB関係はセキュリティルールで守るためキーをコードに書いても大丈夫です。もちろんdotenvとか使ってもいいかもしれません。

javascript - Is it safe to expose Firebase apiKey to the public? - Stack Overflow

また、プラグインの作成は以下の記事を参考にさせていただきました。

Nuxt + Firebase Authentication | shimar's blog


3. ユーザデータの保存の準備

ログインしたあとにユーザ認証データをユーザの端末に保存するためvuexを使います。

~/store/index.jsに以下のようにかきます。


~/store/index.js

export const strict = false

export const state = () => ({
user: null,
})

export const mutations = {
setUser (state, payload) {
state.user = payload
}
}

export const actions = {
setUser ({ commit }, payload) {
commit('setUser', payload)
}
}

export const getters = {
isAuthenticated (state) {
return !!state.user
}
}


Firebaseで認証を行うのですが、行なったあとにこのsetUserを用いて認証したデータを保存しておきます。認証が必要な各ページではこのuserデータを読み込みをログイン済みか検証します。

またVuexはロードするとデータが消えてしまうので、消さないようにユーザのlocalStorageを用いていい感じに保存できるようにしておきます。そのためにvuex-persistedstateを使います。

以下の記事を参考にnpm install vuex-persistedstate --savepersistedstate.jsの作成、nuxt.config.js内の設定変更を行なってください。良記事感謝感激謝謝茄子

nuxt.jsを使う時にlocalStorageでstoreを永続化する - Qiita

これでロードしてもVuexのデータが消えなくなりました!

ただひとつ難点があり、createdやmounted時にVuexの値が取得できません。以下の記事を参考

あーありがち - Vuex PersistedStateの値の復帰のタイミングが謎だったのでNuxtのplugin書いて解決した

そのため、Vuexの値をページロード時に最初に取得し、なにかする場合には以下のように書く必要があります。(他の書き方もあります)

import { mapState } from 'vuex'

export default {
computed: {
...mapState(['user'])
},
mounted() {
console.log(this.user) // ここだと取得できない
setTimeout(() => {
console.log(this.user) // ここだと取得できる
// なにかしらの処理
})
}
}


4. 認証画面、認証コンポーネントの作成

まずログイン画面(/account)のページを作成します。

ログインはメールアドレスとパスワードでログインすることを想定しています。


~/pages/account.vue

<template>

<div>
<!-- ログイン中に表示される画面 -->
<div v-if="isAuthenticated">
{{ user.email }}でログイン中です<br>
<button v-on:click="logout">ログアウト</button><br>
<a href="/member-page">メンバーページへ</a>
</div>
<!-- ログインしていない時に表示される画面 -->
<div v-else>
メール<br>
<input type="text" v-model="email"><br>
パスワード<br>
<input type="password" v-model="password"><br>
<button v-on:click="login">ログイン</button>
</div>
</div>
</template>

<script>
import firebase from '~/plugins/firebase'
import { mapActions, mapState, mapGetters } from 'vuex'
export default {
data() {
return {
email: '',
password: ''
}
},
computed: {
...mapState(['user']),
...mapGetters(['isAuthenticated'])
},
mounted() {
firebase.auth().onAuthStateChanged((user) => {
this.setUser(user)
})
},
methods : {
...mapActions(['setUser']),
login() {
firebase.auth().signInWithEmailAndPassword(this.email, this.password)
.then(user => {
// ログインしたら飛ぶページを指定
// this.$router.push("/member-page")
}).catch((error) => {
alert(error)
});
},
logout() {
firebase.auth().signOut()
.then(() => {
this.setUser(null)
}).catch((error) => {
alert(error)
})
}
}
}
</script>


実際に作成したaccountページのhttp://localhost:3000/accountに飛ぶと以下のような感じ

スクリーンショット 2018-12-13 13.18.47.png

おっと、まだアカウントを作成していませんでしたね!(海外のライブラリのチュートリアルありがちな唐突な気さくさ)

Firebaseの管理画面でAuthenticationの項目のログイン方法のメール/パスワードを有効にしておく必要があります。ここで、Firebaseの管理画面のAuthenticationの項目のユーザから適当にユーザを追加しておいてください。

スクリーンショット 2018-12-13 11.24.34.png

スクリーンショット 2018-12-13 11.25.20.png

これでログインできるようになりました。

実際に作成したアカウントでログインをしてみると以下のような感じです。

スクリーンショット 2018-12-13 13.19.36.png

実際にサービスにする際には新規アカウント作成画面を作ったりしてあげる必要があります。(余裕があれば書きたい)

また、GoogleやTwitter,Facebookアカウントを利用したログインとかもFirebaseでめっちゃ簡単に実装できます。スマホユーザのことを考えると該当のログイン画面にリダイレクトしてあげて本サービスにまたリダイレクトしてあげる必要があります。このリダイレクトがめんどくて、サービスのルートにリダイレクトされるので、そこらへんをいい感じに書いてあげる必要があります。(余裕があれば書きたい)


5. 認証が必要な画面を簡単に作成

まず認証をしていないと見れないコンポーネント(認証していないとログイン画面に飛ばされるコンポーネント)を作成します。

componentsmembers-only.vueを作成して以下のコードを書きます。


~/components/members-only.vue

<template>

<div v-if="loaded">
<slot/>
</div>
</template>

<script>
import { mapGetters, mapState } from 'vuex'

export default {
data() {
return {
loaded: false
}
},
computed: {
...mapGetters(['isAuthenticated'])
},
async mounted() {
setTimeout(() => {
if (!this.isAuthenticated) {
// ログインしていなかったら飛ぶページを設定
this.$router.push('account')
}
this.loaded = true
}, 0)
}
}
</script>


以上のコンポーネントを用いて、実際にログインしていないと見れないページを以下のように作成します。


~/pages/member-page.vue

<template>

<members-only>
<h1>ログインしていないと見れないページ</h1>
<a href="/account">アカウントページへ</a>
</members-only>
</template>

<script>
import MembersOnly from '~/components/members-only.vue'

export default {
components: {
MembersOnly
}
}
</script>


実際に作成したmember-pageページのhttp://localhost:3000/member-pageに飛ぶと以下のような感じ

ログインしているとちゃんと表示されます。ログインしていないとaccountページに飛ばされます。

スクリーンショット 2018-12-13 13.20.52.png

members-onlyコンポーネントを作成したことによって、ログインしていないと見れないページを簡単に作成できました。コンポーネントをロードして、該当のページの親要素にしてあげるだけです。

ちなみにユーザ側でVuexの値はいじることができます。そのため実際はログインしていなくても、ログインしているように見せかけてmembers-onlyコンポーネントをつけた内部をみることができます。

FirebaseではDBにセキュリティルールをつけて、ユーザごとの読み書き制限ができます。そのため、以上のような場合でもきちんとデータ側にセキュリティルールをつけておけばいいということになります。つまり悪意のあるユーザが自分のブラウザ上でVuexの値をいじっても、セキュリティルールで守られたデータは取得できないので、大丈夫ということになります。

おわり。


番外編 1ヶ月前の自分に教えたいリンク集

FirebaseのDBやってると「は?リレーション組めないやんけ💩」ってなると思います。それはSQLマンです。Firebaseでは非正規化が正規化です。ってのを教えてくれるFirebaseの公式のYouTubeの動画がコレです。↓

言語は英語ですが、ただしい日本語の字幕がついてるので問題ないです。

SQL Databases and the Firebase Database - The Firebase Database For SQL Developers #1 - YouTube

NoSQLはわかった、でもこれってどうやって読み書き制限すればええんか?ってなった時に使えるのが↓

先ほども触れましたが、FirebaseはAPIキーはアプリケーションを特定するためだけに使い読み書き制限のために用いません。

FirebaseではDBの読み書き制限はセキュリティルールを記述することによって行います。

Firebase Realtime Database ルールについて  |  Firebase Realtime Database  |  Firebase

firebase realtime database でよく使う rule - Qiita

FirebaseのDBのクエリの公式リファレンスのサンプル少なくない?ってなった時に使えるのが↓です。

公式リファレンスはわかりやすくするため(?)、必要最低限のことが書いてあります。そのためこれをこうしたいっていうのはググるといいスニペットが出てきます。

SQLをFirebaseで書き換えるためのチート集 - Qiita