最初に
この記事はVue.js アドベントカレンダー#4 25日目の記事です。
この一ヶ月でVue.jsについてのナレッジが100記事増えたことになります。やったね!!
2018/8/2 追記
現在では vue-cli
の3.x.xがリリースされており、スキャフォールドからVueプロジェクト作成の工程が若干異なりますのでご留意ください。
この記事でやること
バックエンドをFirebaseに丸投げしたユーザ登録 → サインイン → サインアウト までのチュートリアルです。
認証の実装は面倒
フロントエンドの技術を使ってちょっとしたアプリケーションを作った時、認証やユーザ管理を実装するのはそれなりに面倒かと思います。フレームワークの選定、DBは何使う?など決めなければならないこと、覚えなくてはならないことがたくさんありますね。
そこでBaaSを使います。
BaaSって?
Backend as a Service のことを指します。
サービスそのものがバックエンドとしての機能を備えたサービスのことを指します。流行りの「○○ as a Service」のリテラルですね。
なんのこっちゃ分からなくても言葉に惑わされることなく、とりあえず使ってみれば理解が早いと思います。
今回は「 Firebase 」を使ってユーザ認証を実装していきたいと思います。
Firebaseとは
Googleが買収したmBaaS(mobile Backend as a Service)です。
Firebaseについては色々の方がすでに言及されているので、そちらを参照のこと。
成熟を待たずしてサービスや技術が批評されるのはフロントエンドあるあるっぽいです。技術を信じるお前を信じる強い心が必要です。バックエンド屋でよかった。
環境
せっかくなのでパッケージ管理には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
この画面が表示されれば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に対して、出力するコンポーネントを定義します。
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に対応するコンポーネントを作成して行きます。
登録画面をつくる
さくっと登録画面を作ります。
<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>
認証画面をつくる
登録画面のコピペで認証画面もつくります。
<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でプロジェクトを作成
コンソールにログインしてプロジェクトを作成します。
こんな画面になればOKです。
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
// 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 プロジェクト画面に戻って認証を有効にします。
ここではメール認証を有効にしておきます。
ユーザ登録のコードを追加する
firebase.auth().createUserWithEmailAndPassword() を使い、ユーザ登録のメソッドを追加します。
<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>
ユーザ登録を試してみます。
できました!
ユーザ認証のコードを組み込む
firebase.auth().signInWithEmailAndPassword()を使い、認証のメソッドを追加します。
<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>
認証 => ルートへリダイレクトまでやってみます。
できましたね!!
vue-routerでリダイレクトの設定をする
メタフィールドを設定します。
ルートの定義をする際に meta フィールドを含めることができます。
引用: ルートメタフィールド
サンプルの通りですが、requiresAuthをメタフィールドに定義しておきます。
また、Firebase側から、現在のユーザを取得し、 currentUser && requiresAuth
ならアクセス可、それ以外は /signinへリダイレクトされるようにしましょう。
currentUser プロパティを使用しても、現在ログインしているユーザーを取得できます。ユーザーがログインしていない場合、currentUser は null です。
firebase.auth().currentUser から現在のユーザを取得できます。
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 メッセージを追加します。
<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