JavaScript
vue.js
Firebase
vue-router
vue-cli
Vue.js #4Day 25

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

最初に

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

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

この記事でやること

バックエンドを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 でユーザーを管理する

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