Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

最初に

この記事は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

sin_tanaka
音楽全般好きで、Pythonで音声解析をしていました。 サーバーサイドを経験後、現在はフロントエンドを中心に開発しています。 持ち前の傲岸不遜・平身低頭で乗り切っています。
https://blog.sin-tanaka.com/
jsl
株式会社日本システム技研は、1976年に設立した長野に本社を構えるIT企業です。主にPython/Djangoの使用したWebアプリケーションの受託開発とプロダクト開発を行なっています。エンジニア向けコミュニティGEEKLAB.NAGANO( http://geeklab-nagano.com ) の運営をしています。
https://jsl.co.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした