Quasar Framework(Vue.js) + Firebaseで、マテリアルデザインなログインフォームを実装する

Vue.jsでマテリアルデザイン風なサイトが手軽に構築できるQuasar FrameworkとFirebaseで、ログイン機能を実装してみました。

# Quasar Frameworkとは?
レスポンシブ対応で、マテリアルデザイン風のサイトが手軽に構築できるVue.js搭載のデザインフレームワークです。中身はVue.jsですので、Vue.jsが使える方は抵抗なく使えると思います。

# Firebaseとは?
googleが提供するMBaasです。主にモバイル用途のようなのですが、普通に小さなウェブアプリとかなら使えるぽいです。

# 参考
http://quasar-framework.org/
https://medium.com/@anas.mammeri/vue-2-firebase-how-to-build-a-vue-app-with-firebase-authentication-system-in-15-minutes-fdce6f289c3c
https://qiita.com/sin_tanaka/items/ea149a33bd9e4b388241

# 環境
* macOS High Sierra 10.13.2
* npmがinstallされている

# Quasar Framework をインストール・準備する
インストールして、開発サーバを起動させて動作を確認します。

```shell
# quasar-cliをinstall
$ npm install -g quasar-cli

# quasar init <スターターキット名> <プロジェクト名> 
$ quasar init default quasar-firebase

# プロジェクトフォルダに移動する。
$ cd quasar-firebase

# npmのpackageをinstallする。
$ npm install

# 開発サーバーでの起動
$ quasar dev
```

`http://localhost:8080/`にアクセスして、デモ画面が表示されていれば成功！

# Signup.vue, Login.vueを作成する

```html:Singup.vue
<template lang="html">
  <div>
    <div class="layout-padding row justify-center background-whitesmoke items-center">
      <div class="col-xs-12 col-sm-7 col-lg-5">
        <q-card class="q-card-background-white">
          <q-card-main>
            <div class="row">
              <div class="col-sm-10 offset-sm-1 text-center">
                <img src="~assets/quasar-logo-full.svg" class="logo-sized">
              </div>
            </div>
            <div class="row">
              <div class="col-sm-10 offset-sm-1 text-left">
                <h5>新規登録</h5>
              </div>
            </div>
            <div class="row">
              <div class="col-sm-10 offset-sm-1 text-left">
                <p style="font-size: 12px;">新規アカウントの登録をお願いします。</p>
              </div>
            </div>
            <div class="row">
              <div class="col-sm-10 offset-sm-1">
                <q-field>
                  <q-input v-model="email" float-label="メールアドレス" color="primary" type="email"/>
                </q-field>
              </div>
            </div>
            <div class="row">
              <div class="col-sm-10 offset-sm-1">
                <q-field>
                  <q-input v-model="password" float-label="パスワード" color="primary" type="password"/>
                </q-field>
              </div>
            </div>
            <div class="row" style="padding-top: 20px;">
              <div class="col-sm-10 offset-sm-1 text-right">
                <q-btn color="primary" style="font-weight: bold;" @click="signUp">新規登録</q-btn>
              </div>
            </div>
            <br>
            <div class="row">
              <p style="color:#027be3;font-size:12px;"><router-link to="/login">すでに登録済みの方はサインインしてください</router-link></p>
            </div>
          </q-card-main>
        </q-card>
      </div>
    </div>
  </div>
</template>

<script>
import {
  QBtn,
  QCard,
  QCardMain,
  QField,
  QInput
} from 'quasar'

export default {
  name: 'Signup',
  components: {
    QBtn,
    QCard,
    QCardMain,
    QField,
    QInput
  },
  data () {
    return {
      password: '',
      email: ''
    }
  }
}
</script>

<style lang="stylus">
.q-card-background-white
  background white

.background-whitesmoke
  background whitesmoke
  height 100vh

.logo-sized
  width 150px
  margin 20px
</style>
```

```html:Login.vue
<template lang="html">
  <div>
    <div class="layout-padding row justify-center background-whitesmoke items-center">
      <div class="col-xs-12 col-sm-7 col-lg-5">
        <q-card class="q-card-background-white">
          <q-card-main>
            <div class="row">
              <div class="col-sm-10 offset-sm-1 text-center">
                <img src="~assets/quasar-logo-full.svg" class="logo-sized">
              </div>
            </div>
            <div class="row">
              <div class="col-sm-10 offset-sm-1 text-left">
                <h5>ログイン</h5>
              </div>
            </div>
            <div class="row">
              <div class="col-sm-10 offset-sm-1 text-left">
                <p style="font-size: 12px;">メールアドレスとパスワードを入力してください。</p>
              </div>
            </div>
            <div class="row">
              <div class="col-sm-10 offset-sm-1">
                <q-field>
                  <q-input v-model="email" float-label="メールアドレス" color="primary" type="email"/>
                </q-field>
              </div>
            </div>
            <div class="row">
              <div class="col-sm-10 offset-sm-1">
                <q-field>
                  <q-input v-model="password" float-label="パスワード" color="primary" type="password"/>
                </q-field>
              </div>
            </div>
            <div class="row" style="padding-top: 20px;">
              <div class="col-sm-10 offset-sm-1 text-right">
                <q-btn color="primary" style="font-weight: bold;" @click="signIn">ログイン</q-btn>
              </div>
            </div>
            <br>
            <div class="row">
              <p style="color:#027be3;font-size:12px;"><router-link to="/signup">新規アカウント作成</router-link></p>
            </div>
          </q-card-main>
        </q-card>
      </div>
    </div>
  </div>
</template>

<script>
import {
  QBtn,
  QCard,
  QCardMain,
  QField,
  QInput
} from 'quasar'

export default {
  name: 'Login',
  components: {
    QBtn,
    QCard,
    QCardMain,
    QField,
    QInput
  },
  data () {
    return {
      email: '',
      password: ''
    }
  }
}
</script>

<style lang="stylus">
.q-card-background-white
  background white

.background-whitesmoke
  background whitesmoke
  height 100vh

.logo-sized
  width 150px
  margin 20px
</style>
```

## vue-routerの設定
`quasar init` で作成されたプロジェクトにはvue-routerも自動でインストールされていると思います。src/router.jsに設定します。

* mode: 'history' に変更します。
* routesに、login, singupのrouteを追記します。

```javascript:router.js
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

function load (component) {
  // '@' is aliased to src/components
  return () => import(`@/${component}.vue`)
}

export default new VueRouter({
  mode: 'history',
  scrollBehavior: () => ({ y: 0 }),

  routes: [
    { path: '/', component: load('Hello') },
    { path: '/login', name: 'login', component: load('Login') },
    { path: '/signup', name: 'singup', component: load('Signup') },
    // Always leave this last one
    { path: '*', component: load('Error404') } // Not found
  ]
})
```

## 確認
ターミナルで、`$ quasar dev`を実行している状態で、
http://localhost:8080/signup
http://localhost:8080/login
にそれぞれ、以下のように表示されれば成功です！

![screen.png](https://qiita-image-store.s3.amazonaws.com/0/125574/a9796d77-012f-689d-aae3-7bac2e270dd1.png)


# Firebaseのインストール・設定

## npmで、firebaseをインストールする

```shell
$ npm install --save firebase
```

## Firebaseのプロジェクトの作成・設定
* 次に、Firebaseのコンソール画面にログインします。(https://console.firebase.google.com/)
* Firebaseのコンソール画面で、「プロジェクトを追加」から、プロジェクト名と、国/地域　を選択して、プロジェクトを作成します。
※コンソールへのログインは、googleアカウントが必要です。

* 「ウェブアプリにFirebaseを追加する」をクリックします。

![スクリーンショット 2018-01-25 21.26.39.png](https://qiita-image-store.s3.amazonaws.com/0/125574/9b1ebf62-2f1f-72e5-e729-dc6b07525a9e.png)


* 以下のようなコードが表記されると思います。

```html
<script src="https://www.gstatic.com/firebasejs/4.8.1/firebase.js"></script>
<script>
  var config = {
    apiKey: "YOUR_KEY",
    authDomain: "quasar-YOUR_DOMAIN.firebaseapp.com",
    databaseURL: "https://YOUR_DOMAIN.firebaseio.com",
    projectId: "YOUR_ID",
    storageBucket: "YOUR_BUCKET_ID.appspot.com",
    messagingSenderId: "YOUR_SENDER_ID"
  };
  firebase.initializeApp(config);
</script>
```

* src/main.jsに以下の３点を追記します。
    * config部分をコピペする
    * firebaseをimportする
    * firebaseのinitializeAppを実行する

```javascript:src/main.js
require(`quasar/dist/quasar.${__THEME}.css`)

import Vue from 'vue'
import Quasar from 'quasar'
import router from './router'

// firebaseをimportします
import firebase from 'firebase'

Vue.config.productionTip = false
Vue.use(Quasar) // Install Quasar Framework

if (__THEME === 'mat') {
  require('quasar-extras/roboto-font')
}
import 'quasar-extras/material-icons'

// config部分をコピペします
var config = {
  apiKey: 'YOUR_KEY',
  authDomain: 'quasar-firebase.firebaseapp.com',
  databaseURL: 'https://YOUR_DOMAIN.firebaseio.com',
  projectId: 'YOUR_ID',
  storageBucket: 'YOUR_BUCKET_ID.appspot.com',
  messagingSenderId: 'YOUR_SENDER_ID'
}

// firebaseの初期化設定します
firebase.initializeApp(config)

Quasar.start(() => {
  /* eslint-disable no-new */
  new Vue({
    el: '#q-app',
    router,
    render: h => h(require('./App').default)
  })
})
```

## Firebaseの認証の設定
Firebaseのコンソール画面から、プロジェクト画面を開いて、「Authentication」にメール/パスワードの認証を有効にします。

![スクリーンショット 2018-01-25 21.02.16.png](https://qiita-image-store.s3.amazonaws.com/0/125574/948ec16a-2ea3-e8b3-a947-d5d079f51d62.png)


## 新規ユーザーの登録メソッドを追記

* `firebase.auth().createUserWithEmailAndPassword()`を使います。

```html:Signup.vue
import firebase from 'firebase'
...
<script>
...
  methods: {
    signUp: function () {
      firebase.auth().createUserWithEmailAndPassword(this.email, this.password)
        .then(user => {
          alert('アカウントの新規作成が完了しました！', user.email)
        })
        .catch(error => {
          var errorCode = error.code
          var errorMessage = error.message
          alert(errorCode, errorMessage)
        })
    }
  }
...
</script>
...
```

## ユーザー認証のメソッドを追記する。

* `firebase.auth().signInWithEmailAndPassword`を利用して

```html:Login.vue
...
<script>
...
import firebase from 'firebase'
...
  methods: {
    signIn: function () {
      firebase.auth().signInWithEmailAndPassword(this.email, this.password)
        .then(user => {
          alert('無事ログインしました!')
        })
        .catch(err => {
          alert(err)
        })
    }
...
</script>
...
```

##  

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

* src/routes.js に次のように設定します。

```javascript:routes.js
...
  routes: [
    { path: '/', component: load('Hello'), meta: { requiresAuth: true } },
    { path: '/login', name: 'login', component: load('Login') },
    { path: '/signup', name: 'singup', component: load('Signup') },
    // Always leave this last one
    { path: '*', component: load('Error404') } // Not found
  ]
...
```

* さらに、src/main.jsに、ログインしていない状態でホーム画面にアクセスすると、login画面にリダイレクトするように設定します。

```javascript:main.js
...
firebase.initializeApp(config)

// ここを追加
router.beforeEach((to, from, next) => {
  let currentUser = firebase.auth().currentUser
  let requiresAuth = to.matched.some(record => record.meta.requiresAuth)

  if (requiresAuth && !currentUser) {
    next('login')
  }
  else if (!requiresAuth && currentUser) {
    next('hello')
  }
  else {
    next()
  }
})

Quasar.start(() => {
  /* eslint-disable no-new */
  new Vue({
    el: '#q-app',
    router,
    render: h => h(require('./App').default)
  })
})
```

* `http://localhost:8080/`にアクセス→`http://localhost:8080/login`にリダイレクト→ログイン実行→ログイン成功のalertが表示されて、`http://localhost:8080/`にアクセスされていれば成功です！ただ、リロードすると、またもやログイン画面に戻ってしまいます。。。これを解決するには以下の手順が必要です。

## routerの再設定
さきほどセットしたbeforeEachは、firebaseの初期化の前に呼び出されているので、初期化が完了したときには、firebase.auth（）.currentUserがnullを返してしまい、それが原因で、ログイン画面にリダイレクトされているようです。

Firebaseでは、AuthオブジェクトにObserverを設定することができます。onAuthStateChanged を利用して、Firebaseが初期化されたことが確認された場合にのみ、Vueアプリケーションを初期化するようにします。（Authの状態が中途半端な状態にならないということかな。）

main.jsに、コードを追記します。

```javascript:main.js
let app
firebase.auth().onAuthStateChanged(function (user) {
  Quasar.start(() => {
    if (!app) {
      /* eslint-disable no-new */
      app = new Vue({
        el: '#q-app',
        router,
        render: h => h(require('./App').default)
      })
    }
  })
})
```

## ログアウト機能をHello.vueに追記する。
Hello.vueにログアウト機能を実装します。今回はq-toolbarの右端にログアウトボタンをつけてみました。

```html:Hello.vue
...
<q-toolbar slot="header" class="glossy">
      <q-btn
        flat
        @click="$refs.layout.toggleLeft()"
      >
        <q-icon name="menu" />
      </q-btn>

      <q-toolbar-title>
        Quasar App
        <div slot="subtitle">Running on Quasar v{{$q.version}}</div>
      </q-toolbar-title>
      <q-btn outline @click="logout">ログアウト</q-btn>
    </q-toolbar>

...

import firebase from 'firebase'

...

logout () {
      firebase.auth().signOut()
        .then(() => {
          alert('ログアウトしました!')
          this.$router.replace('login')
        })
    }
...
```

これでサインアップ、ログイン、ログアウトまで、ひとまず完成です！

# まとめ
サインアップ、ログイン、ログアウトまで大体１５分くらいで、できてしまうので、firebaseかなり便利そうだな〜という印象です。

Quasar Frameworkも、ほとんどvue.jsの知識でサクッといい感じのデザインのページが作れてしまうので、マテリアルデザインに抵抗が無い人はおすすめです！