Edited at
Vue.js #4Day 25

Vue.js + Firebase を使って爆速でユーザ認証を実装する


最初に

この記事はVue.js アドベントカレンダー#4 25日目の記事です。

この一ヶ月でVue.jsについてのナレッジが100記事増えたことになります。やったね!!


2018/8/2 追記

現在では vue-cli の3.x.xがリリースされており、スキャフォールドからVueプロジェクト作成の工程が若干異なりますのでご留意ください。



この記事でやること

バックエンドをFirebaseに丸投げしたユーザ登録 → サインイン → サインアウト までのチュートリアルです。

1513916508.gif


認証の実装は面倒

フロントエンドの技術を使ってちょっとしたアプリケーションを作った時、認証やユーザ管理を実装するのはそれなりに面倒かと思います。フレームワークの選定、DBは何使う?など決めなければならないこと、覚えなくてはならないことがたくさんありますね。

そこでBaaSを使います。


BaaSって?

Backend as a Service のことを指します。

サービスそのものがバックエンドとしての機能を備えたサービスのことを指します。流行りの「○○ as a Service」のリテラルですね。

なんのこっちゃ分からなくても言葉に惑わされることなく、とりあえず使ってみれば理解が早いと思います。

今回は「 Firebase 」を使ってユーザ認証を実装していきたいと思います。


Firebaseとは

Googleが買収したmBaaS(mobile Backend as a Service)です。

Firebaseについては色々の方がすでに言及されているので、そちらを参照のこと。

Firebaseの始め方

Firebaseをやめた4つの理由

それでもFirebaseを使うべき5つのメリット

成熟を待たずしてサービスや技術が批評されるのはフロントエンドあるあるっぽいです。技術を信じるお前を信じる強い心が必要です。バックエンド屋でよかった。


環境

せっかくなのでパッケージ管理にはyarnを使います。

# Nodejs

$ node -v
v8.7.0
# vue-cli
$ vue -V
2.9.2
# yarn
$ yarn -v
1.3.2


フロント部を実装する

まずはログイン画面など作っていきます。


vue-cliでプロジェクトを作成

vue-cliというスキャフォールドでプロジェクトを作成して行きます。特に指定ありませんがvue-routerを使う設定だけはYesにしましょう。

ここではプロジェクト名を vue_firebase_auth_test にしています。

$ vue init webpack vue_firebase_auth_test

? Project name vue_firebase_auth_test
? Project description A Vue.js project
? Author sin_tanaka
? Vue build standalone
? Install vue-router? Yes
? Use ESLint to lint your code? Yes
? Pick an ESLint preset Standard
? Set up unit tests No
? Setup e2e tests with Nightwatch? No
? Should we run `npm install` for you after the project has been created? (recommended) ya
rn

vue-cli · Generated "vue_firebase_auth_test".

# Installing project dependencies ...
# ========================

yarn install v1.3.2
info No lockfile found.
[1/5] 🔍 Validating package.json...
[2/5] 🔍 Resolving packages...
[3/5] 🚚 Fetching packages...
[4/5] 🔗 Linking dependencies...
[5/5] 📃 Building fresh packages...
success Saved lockfile.
✨ Done in 92.66s.

Running eslint --fix to comply with chosen preset rules...
# ========================

yarn run v1.3.2
$ eslint --ext .js,.vue src --fix
✨ Done in 12.88s.

# Project initialization finished!
# ========================

To get started:

cd vue_firebase_auth_test
npm run dev

Documentation can be found at https://vuejs-templates.github.io/webpack

いつのまにかパッケージのインストールまで終わらせくれるようになっていました。

ローカルサーバを立ち上げます。

$ cd vue_firebase_auth_test

$ yarn dev

スクリーンショット 0029-12-21 午後4.57.06.png

この画面が表示されればOKです。

ついでにプロジェクトの構造を見ておきます。

$ tree -I node_modules

.
├── README.md
├── build
│   ├── build.js
│   ├── check-versions.js
│   ├── logo.png
│   ├── utils.js
│   ├── vue-loader.conf.js
│   ├── webpack.base.conf.js
│   ├── webpack.dev.conf.js
│   └── webpack.prod.conf.js
├── config
│   ├── dev.env.js
│   ├── index.js
│   └── prod.env.js
├── index.html
├── package.json
├── src
│   ├── App.vue
│   ├── assets
│   │   └── logo.png
│   ├── components
│   │   └── HelloWorld.vue
│   ├── main.js
│   └── router
│   └── index.js
├── static
└── yarn.lock


vue-routerでルーティングを決める

次にルーティングを決めておきます。

認証してない状態で / にアクセスすると /signin にリダイレクト、どの画面でもsignoutしたら /signin にリダイレクトされるようなアプリとします。

path
説明

/
表示に認証が必要なコンテンツ

/signup
ユーザ登録

/signin
サインイン

では vue-routerでpathに対して、出力するコンポーネントを定義します。


src/router/index.js

import Vue from 'vue'

import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import Signup from '@/components/Signup'
import Signin from '@/components/Signin'

Vue.use(Router)

export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
},
{
path: '/signup',
name: 'Signup',
component: Signup
},
{
path: '/signin',
name: 'Signin',
component: Signin
}
]
})


Signin, Signupに対応するコンポーネントを作成して行きます。


登録画面をつくる

さくっと登録画面を作ります。

スクリーンショット 0029-12-22 午前9.58.44.png


src/components/Signup.vue

<template>

<div class="signup">
<h2>Sign up</h2>
<input type="text" placeholder="Username" v-model="username">
<input type="password" placeholder="Password" v-model="password">
<button>Register</button>
<p>Do you have an account?
<router-link to="/signin">sign in now!!</router-link>
</p>
</div>
</template>

<script>
export default {
name: 'Signup',
data () {
return {
username: '',
password: ''
}
},
methods: {}
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
.signup {
margin-top: 20px;

display: flex;
flex-flow: column nowrap;
justify-content: center;
align-items: center
}
input {
margin: 10px 0;
padding: 10px;
}
</style>



認証画面をつくる

登録画面のコピペで認証画面もつくります。

スクリーンショット 0029-12-22 午前9.54.20.png


src/components/Signin.vue

<template>

<div class="signin">
<h2>Sign in</h2>
<input type="text" placeholder="Username" v-model="username">
<input type="password" placeholder="Password" v-model="password">
<button>Signin</button>
<p>You don't have an account?
<router-link to="/signup">create account now!!</router-link>
</p>
</div>
</template>

<script>
export default {
name: 'Signin',
data () {
return {
username: '',
password: ''
}
},
methods: {}
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
.signin {
margin-top: 20px;

display: flex;
flex-flow: column nowrap;
justify-content: center;
align-items: center
}
input {
margin: 10px 0;
padding: 10px;
}
</style>


http://localhost:8080/#/signup

http://localhost:8080/#/signin

にアクセスして確認しておきます。


バックエンド部を実装する

画面ができたのでバックエンドをFirebaseで設定します。


Firebaseでプロジェクトを作成

コンソールにログインしてプロジェクトを作成します。

スクリーンショット 0029-12-22 午前10.05.34.png

こんな画面になればOKです。

スクリーンショット 0029-12-22 午前10.03.50.png


Firebaseの設定をプロジェクトに追加する

WebアプリにFirebaseを追加する をクリックします。

ドキュメント: https://firebase.google.com/docs/auth/web/password-auth?authuser=1

<script src="https://www.gstatic.com/firebasejs/4.8.1/firebase.js"></script>

<script>
// Initialize Firebase
var config = {
apiKey: "YOUR_KEY",
authDomain: "YOUR_DOMAIN.firebaseapp.com",
databaseURL: "YOUR_DOMAIN.firebaseio.com",
projectId: "YOUR_ID",
storageBucket: "YOUR_BUCKET_ID.appspot.com",
messagingSenderId: "YOUR_SENDER_ID"
};
firebase.initializeApp(config);
</script>

プロジェクト固有のconfigが表示されるのでjsに組み込みます。

ここでfirebaseのモジュールを組み込んでいない人はインストールしておきます。

https://firebase.google.com/docs/web/setup?authuser=0

$ yarn add firebase --save


src/main.js

// The Vue build version to load with the `import` command

// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import firebase from 'firebase'

Vue.config.productionTip = false

const config = {
apiKey: 'YOUR_KEY',
authDomain: 'YOUR_DOMAIN.firebaseapp.com',
databaseURL: 'YOUR_DOMAIN.firebaseio.com',
projectId: 'YOUR_ID',
storageBucket: 'YOUR_BUCKET_ID.appspot.com',
messagingSenderId: 'YOUR_SENDER_ID'
}
firebase.initializeApp(config);

/* eslint-disable no-new */
new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
})



認証の設定

またFirebase プロジェクト画面に戻って認証を有効にします。

ここではメール認証を有効にしておきます。

スクリーンショット 0029-12-22 午前10.34.53.png


ユーザ登録のコードを追加する

firebase.auth().createUserWithEmailAndPassword() を使い、ユーザ登録のメソッドを追加します。


src/components/Signup.vue

<template>

<div class="signup">
<h2>Sign up</h2>
<input type="text" placeholder="Username" v-model="username">
<input type="password" placeholder="Password" v-model="password">
<button @click="signUp">Register</button>
<p>Do you have an account?
<router-link to="/signin">sign in now!!</router-link>
</p>
</div>
</template>

<script>
import firebase from 'firebase'

export default {
name: 'Signup',
data () {
return {
username: '',
password: ''
}
},
methods: {
signUp: function () {
firebase.auth().createUserWithEmailAndPassword(this.username, this.password)
.then(user => {
alert('Create account: ', user.email)
})
.catch(error => {
alert(error.message)
})
}
}
}
</script>

<style scoped>
/* 省略 */
</style>


ユーザ登録を試してみます。

1513916508.gif

できました!


ユーザ認証のコードを組み込む

firebase.auth().signInWithEmailAndPassword()を使い、認証のメソッドを追加します。


src/components/Signin.vue

<template>

<div class="signin">
<h2>Sign in</h2>
<input type="text" placeholder="Username" v-model="username">
<input type="password" placeholder="Password" v-model="password">
<button @click="signIn">Signin</button>
<p>You don't have an account?
<router-link to="/signup">create account now!!</router-link>
</p>
</div>
</template>

<script>
import firebase from 'firebase'

export default {
name: 'Signin',
data: function () {
return {
username: '',
password: ''
}
},
methods: {
signIn: function () {
firebase.auth().signInWithEmailAndPassword(this.username, this.password).then(
user => {
alert('Success!')
this.$router.push('/')
},
err => {
alert(err.message)
}
)
}
}
}
</script>

<style scoped>
/* 省略 */
</style>


認証 => ルートへリダイレクトまでやってみます。

1513909477.gif

できましたね!!


vue-routerでリダイレクトの設定をする

メタフィールドを設定します。


ルートの定義をする際に meta フィールドを含めることができます。

引用: ルートメタフィールド


サンプルの通りですが、requiresAuthをメタフィールドに定義しておきます。

また、Firebase側から、現在のユーザを取得し、 currentUser && requiresAuth ならアクセス可、それ以外は /signinへリダイレクトされるようにしましょう。


currentUser プロパティを使用しても、現在ログインしているユーザーを取得できます。ユーザーがログインしていない場合、currentUser は null です。


firebase.auth().currentUser から現在のユーザを取得できます。


src/router/index.js

import Vue from 'vue'

import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import Signup from '@/components/Signup'
import Signin from '@/components/Signin'
import firebase from 'firebase'

Vue.use(Router)

let router = new Router({
routes: [
{
path: '*',
redirect: 'signin'
},
{
path: '/',
name: 'HelloWorld',
component: HelloWorld,
meta: { requiresAuth: true }
},
{
path: '/signup',
name: 'Signup',
component: Signup
},
{
path: '/signin',
name: 'Signin',
component: Signin
}
]
})

router.beforeEach((to, from, next) => {
let requiresAuth = to.matched.some(record => record.meta.requiresAuth)
let currentUser = firebase.auth().currentUser
if (requiresAuth) {
// このルートはログインされているかどうか認証が必要です。
// もしされていないならば、ログインページにリダイレクトします。
if (!currentUser) {
next({
path: '/signin',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next() // next() を常に呼び出すようにしてください!
}
})

export default router


なお、推奨されているのは以下の方法のようです。

この辺力及ばずよく分からず。。なんとなく変更を監視するのはわかるのですが。。


現在ログインしているユーザーを取得するには、Auth オブジェクトでオブザーバーを設定することをおすすめします。

引用: Firebase でユーザーを管理する



2018/8/2 追記

推奨されている方法について、@makotoyc さんからコメントいただきました。ありがとうございます!(まるっと転記させていただきます…)

firebase.auth().onAuthStateChanged() のコールバックでログインしているユーザを取得し、ユーザの有無で処理を分ける方法が良さそうです

【略】

router.beforeEach((to, from, next) => {
const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
if (requiresAuth) {
// このルートはログインされているかどうか認証が必要です。
// もしされていないならば、ログインページにリダイレクトします。
firebase.auth().onAuthStateChanged(function (user) {
if (user) {
next()
} else {
next({
path: '/signin',
query: { redirect: to.fullPath }
})
}
})
} else {
next() // next() を常に呼び出すようにしてください!
}
})

【略】


HelloWorldコンポーネントにサインアウトボタンと Welcome メッセージを追加します。


src/components/HelloWorld.vue

<template>

<div class="hello">
<h1>Hello {{ name }}!!</h1>
<h1>{{ msg }}</h1>
<h2>Essential Links</h2>
<ul>
<li><a href="https://vuejs.org" target="_blank">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank">Twitter</a></li>
<br>
<li><a href="http://vuejs-templates.github.io/webpack/" target="_blank">Docs for This Template</a></li>
</ul>
<h2>Ecosystem</h2>
<ul>
<li><a href="http://router.vuejs.org/" target="_blank">vue-router</a></li>
<li><a href="http://vuex.vuejs.org/" target="_blank">vuex</a></li>
<li><a href="http://vue-loader.vuejs.org/" target="_blank">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank">awesome-vue</a></li>
</ul>
<button @click="signOut">Sign out</button>
</div>
</template>

<script>
import firebase from 'firebase'

export default {
name: 'HelloWorld',
data () {
return {
msg: 'Welcome to Your Vue.js App',
name: firebase.auth().currentUser.email
}
},
methods: {
signOut: function () {
firebase.auth().signOut().then(() => {
this.$router.push('/signin')
})
}
}
}
</script>

<style scoped>
/* 省略 */
</style>


完成です!


まとめ

いかがだったでしょうか。バックエンドをFirebaseに任せることでかなり簡単にユーザ認証を実装できました。

余談ですが、記事書いてる途中で、ほぼおんなじテーマで記事が公開され、しかも筆者はVue.jsのコミッターやないかい、ということがありました。びっくりすることにタイトルも考えていたものとほぼ同じでした。決してパクリではなく、、

Nuxt.jsとFirebaseを組み合わせて爆速でWebアプリケーションを構築する

Nuxt.jsはほんと使いやすいです。SSRできるけどSPAモードあるし、わりとNuxt.jsでいいんじゃないか感はあります。


参考

Firebase リファレンス: https://firebase.google.com/docs/auth/web/start?authuser=1

vue-router - ナビゲーションガード https://router.vuejs.org/ja/advanced/navigation-guards.html

vue-router - ルートメタフィールド https://router.vuejs.org/ja/advanced/meta.html