27
35

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Vue.js (Vuex, vue-router, vuetify) とFirebaseで始めるユーザー管理

Last updated at Posted at 2019-11-04

概要

昨今、バックエンドがAPIサーバーであることも増えてきたが、認証機能の実装は面倒なことが多い。そのため、認証機能をFirebase Authenticationに任せることも考えられる。本記事では、人気のJavaScriptフレームワークであるVue.jsでフロントエンドのログイン機能を実装し、Firebase Authenticationと連携させる。
また、Vuexを用いたユーザー情報などの状態管理、vue-routerによるルーティングを行い、UIコンポーネントフレームワークとしてVuetifyを使用した。

本記事で説明するシステムは以下の通りです。"/"でホーム画面が描画され、ログイン状態のとき"/test"でもホーム画面が描画される。一方で、ログインしていないときの"/test"では、"/signin"にリダイレクトされログイン画面が表示される。また、"/signup"でサインアップが描画される。
ログイン、サインアップは、firebaseとの連携により実行される。また、ログイン状態は、Vuexにより管理される。

本記事のサンプルは、以下のバージョンを用いた。

npm install -g @vue/cli
vue --version
> 3.10.0

git: k-washi/example-vue-cliの一部が本記事に対応しています。

Vue.jsの設定

本章では、Vue.jsのプロジェクトを作成し、Vuex, vue-router, vuetifyを用いて作成されたプロジェクトを起動させます。

1. プロジェクトの作成

以下のコマンドでプロジェクトを作成する。このとき、Vuex, vue-routerも同時にダウンロードする。

#projectの作成
vue create sample-vue

# 以下、選択肢 (例なので適宜変更してください)
#>Mannually

#>Vuex, Routerを選択 
#(他にも必要なパッケージがあれば選択する。選択例 Babel, Router, Vuex, Linter)

#U>se history mode for router / Y (default)

#>ESLint * Prettier
#>Lint on save
#>In package.json

2. Vuetifyのダウンロード

#package.jsonがあるディレクトリにて
vue add vuetify
#>Default

#もし iconを使用するなら
npm install @mdi/font -D
npm install @mdi/js -D

3. プロジェクトの実行とビルド

以下のコマンドでプロジェクトを実行し、ブラウザでApp running atに記載されたIPにアクセスする。

npm run serve

また、以下のコマンドでビルドすることで、distファイルの配下にindex.htmlやcssファイルなどが作成される。

npm run build

以下の画面が描画結果です。

スクリーンショット 2019-11-05 0.17.41.png

Firebaseの設定

Firebaseのプロジェクト設定

  1. Firebaseにてアカウントを作成し、コンソールに移動する。
  2. プロジェクトを追加する。
  3. Project Overviewの>マークからアプリを登録する (Firebase Hostingはチェックしない)
  4. Authenticationのログイン方法を設定(今回は、メール/パスワードを有効にする)

Vue.jsにインストール

npm install firebase --save

ユーザー管理処理の実装

まず、前提として、emailとパスワードを用いてfirebase側で認証される。そして、その結果、改善検知が可能なJWTが得られる。
ここでは、firebaseと連携したユーザー管理機能として、ユーザーの作成、ログイン、ログアウト、状態確認の機能を実装した。

Vuexを用いたユーザーのログイン状態管理

stateに管理する状態を列挙する。そして、この状態を取得するgettersと状態を更新するmutationsを実装する。
ここでは、emailとログイン状態を管理することとする。

store.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  strict: true,
  state: {
    email: "",
    status: false,
  },
  getters: {
    email(state) {
      return state.email
    },
    isSignedIn(state) {
      return state.status
    },
  },
  mutations: {
    onAuthEmailChanged(state, email) {
      state.email = email; //firebase user情報
    },
    onUserStatusChanged(state, status) {
      state.status = status;
    }
  },
});

Firebaseとの連携部分

まずは、Firebaseと連携するための設定を行う。Project Overviewプロジェクト設定のFirebase SDK snippetに記載されているfirebaseConfigをsample-vue/.env.localに記載する。この.envファイルは、VUE_APPから開始する変数を設定できる。

.env.local
    VUE_APP_FB_KEY=AIzaxxxxxxxxxx
    VUE_APP_FB_DOMEIN=ex-firebase-auth
    VUE_APP_FB_ID=https://ex-firebase-auth
    VUE_APP_FB_PROD_ID=https://ex-firebase-auth
    VUE_APP_FB_BUCKET_ID=ex-firebase-auth.appspot.com
    VUE_APP_FB_SENDER_ID=661xxx18x2

次に、firebaseと連携する処理を実装する。
詳細はコード内に記載。

firebase.js

import firebase from "@firebase/app";
import "@firebase/auth";
import router from "./router"
import store from "./store";
import { createSecretKey } from "crypto";

//process.envでenvファイルに定義した変数にアクセスできる
const firebaseConfig = {
  apiKey: process.env.VUE_APP_FB_KEY,
  authDomain: process.env.VEW_APP_FB_DOMEIN + ".firebaseapp.com",
  databaseURL: process.env.VUE_APP_FB_ID + ".firebaseio.com",
  projectId: process.env.VUE_APP_FB_PROD_ID,
  storageBucket: process.env.VUE_APP_FB_BUCKET_ID,
  messagingSenderId: process.env.VUE_APP_FB_SENDER_ID,
};

export default {
  //読み込み時に、firebase機能の設定をする
  init() {
    firebase.initializeApp(firebaseConfig);
    firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION);
  },

  //emailとパスワードでログインする。
  //firebase認証の結果、JWT Tokenが返ってくる。JWTは、localstrageに保存する。
  //ここで、記載しているrouterは、vue-routerの機能で、'/'へルーティングしている
  signInWithEmailAndPassword(email, password) {
    firebase.auth().signInWithEmailAndPassword(email, password)
      .then(res => {
        res.user.getIdToken().then(idToken => {
          localStorage.setItem('jwt', idToken);
          router.push('/').catch(err => {
            console.log("router push /");
          });
        })
      }, err => {
        console.log(err.message);
      })
  },
  //emailとパスワードでアカウント作成する
  //アカウント作成後は、'/signin'へルーティング
  signUpWithEmailAndPassword(email, password) {
    firebase.auth().createUserWithEmailAndPassword(email, password);
      .then(res => {
        router.push('/signin');
      }).catch(err => {
        console.log(err.message);
      })
  },
  //ログアウト
  //ログアウト後は、保存しているjwtを削除して、vuexのmutationに実装した状態の更新処理でユーザーをログアウト状態にする。
  logOut() {
    firebase.auth().signOut()
      .then(() => {
        localStorage.removeItem("jwt")
        store.commit('onAuthEmailChanged', "");
        store.commit('onUserStatusChanged', false);
      })
      .catch((err) => {
        console.log(`fail logout (${error}) `);
      })
  },
  //状態管理
  //jwtの状態、ユーザーの状態を更新する。
  onAuth() {
    firebase.auth().onAuthStateChanged(user => {
      if (user) {
        if (user.ma) {
          localStorage.setItem('jwt', user.ma);
        } 
        store.commit('onAuthEmailChanged', user.email);
        if (user.uid) {
          store.commit('onUserStatusChanged', true); 
        } else {
          store.commit('onUserStatusChanged', false);
        }
      } else {
        store.commit('onAuthEmailChanged', "");
        store.commit('onUserStatusChanged', false);
      }
    })
  }
}

次にfirebaseとの連携処理にも出てきたルーティング機能を実装する。
今回は、簡単のため、ログイン状態でアクセスしなければならないパスを/testとし、ログイン後は/testでホーム画面が描画されるものとする。
routersにpathとともに、描画するコンポーネントを記載することで, コンポーネント内に記載された<router-view></router-view>の場所にルーティングのコンポーネントを描画する。

path:"*"は、一致するpathがなかった場合のリダイレクトのために設定している。
scrollBehaviorは、戻る処理をしたときに、表示する位置を遷移以前の位置で再度描画するための処理を行う。
router.beforeEacは、ルーティング時のミドルウェアの役割であり、ルーティング処理が実行されるたびに呼び出される。Firebase.onAuth()でユーザーの状態を確認し、選択されたパスへルーティングする。また、meta: {requiresAuth: true}が付随したパスであり、かつ、未ログイン状態の場合、ログイン画面へルーティングされる。

router.js
import Vue from "vue";
import Router from "vue-router";

import Home from "@/views/Home.vue";
import Signup from "@/components/FBSignup.vue";
import Signin from "@/components/FBSignin.vue"

import Firebase from "./firebase"
import store from "./store"

Vue.use(Router);

const router = new Router({
  mode: "history",
  base: process.env.BASE_URL,
  routes: [
    {
      path: "/",
      name: "home",
      component: Home
    },
    {
      path: "/test",
      name: "test",
      component: Home,
      meta: {requiresAuth: true},
    },
    {
      path: "/signup",
      name: "signUp",
      component: Signup,
    },
    {
      path: "/signin",
      name: "signIn",
      component: Signin,
    },
    {
      path: "*",
      redirect: '/'
    },
  ],
  scrollBehavior(to, from, savedPosition) {
    //戻るによる遷移は以前の位置を保持
    if(savedPosition) {
      return savedPosition
    } else {
      if (to.hash) {
        return {selector: to.hast}
      } else {
        return {x: 0, y: 0}
      }
    }
  }
});

router.beforeEach((to, from, next) => {
  Firebase.onAuth();
  let currentUserStatus = store.getters["isSignedIn"];
  let requiresAuth = to.matched.some(record => record.meta.requiresAuth);
  if (!requiresAuth ) {
    next()
  } else if (requiresAuth && !currentUserStatus) {
    next('/signin');
  } else {
    next();
  }
  
})
export default router

最初に読み込んだときに実行する処理をmain.jsに実装した。
ここでは、firebaseの初期化をするとともに、vuex, vue-router, vuetifyをアプリに登録している。
また、firebase.auth().onAuthStateChanged()は、ブラウザの更新を行ったときなどに、非同期に実行されるユーザー情報の更新を描画前に実行するために実装している。

main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import vuetify from "./plugins/vuetify";
import firebase from "firebase/app"

Vue.config.productionTip = false;

Firebase.init();

firebase.auth().onAuthStateChanged(user => {
  if (user) {
    if (user.ma) {
      localStorage.setItem('jwt', user.ma);
    } 
    store.commit('onAuthEmailChanged', user.email);
    if (user.uid) {
      store.commit('onUserStatusChanged', true) 
    } else {
      store.commit('onUserStatusChanged', false)
    }
  } else {
    store.commit('onAuthEmailChanged', "")
  }
  new Vue({
    router,
    store,
    vuetify,
    render: h => h(App)
  }).$mount("#app");

})

最後に、ログイン画面のコンポーネントについて説明する。
本コンポーネントは、ログイン状態の場合、ユーザー情報が描画され、ログイン状態ではない場合、ログインフォームが描画される。userStatusでユーザーのログイン状態を取得しており、usernameでユーザーのemail情報を取得している。

v-*はvuetifyによるコンポーネントです。
<v-btn class="ma-2" outlined color="deep-orange" to="/">はボタンコンポーネントでto="/"でボタンを押した際に移動するパスを指定している。
また、<v-btn class="ma-2" outlined color="deep-orange" @click="signIn">Sign In</v-btn>はクリック時に、methodsに定義したsignInメソッドを実行する。signInは単純にFirebase.jsに定義したログイン関数を使用している。(本当は、フォーム内容で条件分岐が必要かもしれません...)

FBSignin.vue
<template>
  <div>
    <v-btn class="ma-2" outlined color="deep-orange" to="/">
      Home
    </v-btn>
    <p>アカウントを持っていない場合</p>
    <v-btn class="ma-2" outlined color="deep-orange" to="/signup">
      Sign Up
    </v-btn>
  <div v-if="userStatus" key="login">
    <h2>{{username}} がログイン済みです</h2>
  </div>
  <div v-else key="login_process">
    <h2>Sign In</h2>
    <v-form ref="form" v-model="valid" :lazy-validation="lazy">
      <v-col cols="12" sm="6">
        <v-text-field 
          v-model="email" 
          label="email" 
          type="email" 
          required
          ></v-text-field>
      </v-col>
      <v-col cols="12" sm="6">
        <v-text-field 
          v-model="password" 
          label="password" 
          type="password" 
          hint="At least 8 characters"
          :rules="[rules.required, rules.min]" 
          required
        ></v-text-field>
      </v-col>
    <v-btn class="ma-2" outlined color="deep-orange" @click="signIn">Sign In</v-btn>
    
    </v-form>
  </div>
  </div>
</template>

<script>
import Firebase from "../firebase"
export default {
  name: 'Signin',
  data: () => ({
      email: '',
      password: '',
      rules: {
          required: v => !!v||'Required.',
          min: v => v.length >= 8||'Min 8 characters',
      },
      valid: true,
      lazy: false,
  }),
  computed: {
    username() {
      return this.$store.getters.email
    },
    userStatus() {
      //return true in login state
      return this.$store.getters.isSignedIn
    },
  },
  methods: {
    signIn() {
      Firebase.signInWithEmailAndPassword(this.email, this.password);
    },
  },
}
</script>

Appendix

アカウント作成画面などは、ログイン画面と酷似しているため説明を省いた。今回の内容と一致しないが、追加で、k-washi/example-vue-cliに、様々な処理を実装しているので参考にしてみてください。

27
35
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
35

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?