JavaScript
vue.js
Firebase
quasar-framework

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 をインストール・準備する

インストールして、開発サーバを起動させて動作を確認します。

# 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を作成する

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>
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を追記します。
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

Firebaseのインストール・設定

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

$ npm install --save firebase

Firebaseのプロジェクトの作成・設定

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

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

スクリーンショット 2018-01-25 21.26.39.png

  • 以下のようなコードが表記されると思います。
<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に以下の3点を追記します。
    • config部分をコピペする
    • firebaseをimportする
    • firebaseのinitializeAppを実行する
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

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

  • firebase.auth().createUserWithEmailAndPassword()を使います。
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を利用して
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 に次のように設定します。
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画面にリダイレクトするように設定します。
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に、コードを追記します。

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の右端にログアウトボタンをつけてみました。

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')
        })
    }
...

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

まとめ

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

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