VueでFirebaseを使いログイン認証が必要な専用ページを作ろう

#はじめに
今回はVueとVue RouterとFirebaseを用いてログイン認証後の必要ページを作ってみましょう！
前準備の部分が長くなるので、認証後の処理だけを見たい人は[こ↓こ↑](https://qiita.com/okauend/items/c92ec98c258dbe291ff0#%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%AB%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3%E8%AA%8D%E8%A8%BC%E3%82%92%E7%B5%84%E3%81%BF%E8%BE%BC%E3%82%82%E3%81%86)をクリック
#環境
- Vue.js 2(CLI)
- Vue Router
- Firebase
- Yarn

#下準備
##まずは新しいプロジェクト作り
![4be703b086ad849f77fce01da0fdfc7a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257615/be29a1c7-033f-5589-7b45-428653640762.png)
マニュアルで作りましたが、`Router`が必要なのでインストールされてあれば他のをインストールしていても問題ありません。
この記事では、説明のために最小構成でプロジェクトを作成します。
![b5cda66b7fe426d4c00d0d9c9affc9f8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257615/310bac35-d87e-2145-1c03-7941785a785b.png)
次ですが、ここは`history`モードを使うので`y`で。
![655d71fb30622b2d19fca7d2eef1fe27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257615/b0e827a9-003e-3e27-b348-e6fbaae7933d.png)
この後も数回、環境の設定をどうするか聞かれますが、今回のRoutingには影響しないので好みの設定で大丈夫です。
![5197b02c266ef40242a00b41e11d373c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257615/9e67a449-af7a-99f7-9b14-6ed853cdf9a6.png)
無事に作成が完了したら、一度`yarn serve`を行い、バニラ状態のページを確認しておきましょう！
![a34062308900b9e39a4610b3c333a8ef.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257615/db629e16-3bca-9837-fca2-11254064cff5.png)

##Firebaseをインストール
次にFirebase CLIをインストールしましょう。
[Firebase CLI 公式ドキュメント](https://firebase.google.com/docs/cli#setup_update_cli)
###npm
npmの場合ですと、公式ドキュメントにある通り、

**グローバル**

```
npm install -g firebase-tools
```

**ローカル**

```
npm install -D firebase-tools
```

###yarn
yarnを使用している例は公式にはありませんが、いつもの方法でインストールすることは可能です。

**グローバル**

```
yarn global add firebase-tools
```

**ローカル**

```
yarn add firebase-tools
```

###正しくインストールされてるか確認
最後に正しくインストールされてるか確認しましょう。

```
firebase -V
```
と入力して、バージョン表記が表示されれば正しくインストールされています！
(※2020年2月29日現在では、`7.14.0`が最新)

##Firebaseの設定をする
インストールが無事終えたら、次はインストールしたプロジェクトのFirebaseの設定をします。

```
Firebase init
```

このコマンドを打ち込みます。
すると、英語で「初期化を行う手続きを続行してもいいかー？」（意訳）の問が出現し、
その後に、Firebaseのどの機能を使用するか選択する問が現れます。
![abbda024bb2d5a0d8345252559651297.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257615/bd41b42d-8b6e-f7c8-912b-77600f017211.png)

今回は認証だけをやりたいのですが、少なくとも1つは機能を選択しなければいけないため、`Hosting`を選択しました。

次にFirebaseのどのプロジェクトを使用するか？を聞かれます。
![930f375809aeb1f7a759796d9674947c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257615/58ca8428-f4d7-a2c2-d81b-b756f229c6c6.png)

1つ目の選択肢はすでにFirebase上に存在する既存プロジェクトを使用する
2つ目は新しくプロジェクトを作成する
となっています。
もしプロジェクトを作っていなかったら2を、既存のを使用するなら1を選択しましょう。

この後、選択した機能毎をどう設定するかの問が行われ、
その問に答えた後に、**SPA**(シングルページアプリケーション)として設定するかの問が行われます。
**SPA**を使用したいので、これには`y`と入力します。

![a8f960e01b79f035f9c9ccbe0d5962df.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257615/f044d3cd-d59e-3352-98ef-4d1d27753b61.png)
Firebaseの初期化の操作が終わり、このように表示されたら無事完了です！

###Firebase ConsoleからAPI情報を作成
先にAPI情報などを記載する`firebase.js`を作成しましょう。
`src`フォルダ配下に作成します。

```javascript:firebase.js
import firebase from "firebase";
import "firebase/auth";

const config = {
  apiKey: /**/,
  authDomain: /**/,
  databaseURL: /**/,
  projectId: /**/,
  storageBucket: /**/,
  messagingSenderId: /**/,
  appId: /**/,
  measurementId: /**/
};

firebase.initializeApp(config);

export default firebase;
```
`config`の中の情報は、Firebase Consoleの**プロジェクト設定**から取得してきましょう。

`firebase/auth`をImportすることで、**Firebase**の認証機能を使えるようにImportしています。
これらは必ず行うようにしましょう。

これで下準備は完了です！
次から実際に作成していきましょう！

#必要なコンポーネントを作ろう
ルーティングや認証を行う処理などを作る前に、先に必要なコンポーネントを作ります。
必要なコンポーネントは、

- 登録(**sing up**)
- ログイン(**sing in**)
- ログアウト(**sing out**)
- 認証専用ページ

の4つになるので、これらを作っていきましょう。
サイトトップは初期作成で作られたコンポーネントをそのまま使ってしまいましょう。
##コンポーネントを作成
先程必要になる4つのコンポーネントを作っていきましょう。

- 登録(**sing up**)
- ログイン(**sing in**)
- ログアウト(**sing out**)

の3つはコンポーネントは、**Firebase**との通信が必要になるため、
先ほど作っておいたAPI情報を含め初期化しておいた`firebase.js`をImportしておきましょう。
そうしないと、**Firebase**の機能を使うことができません。

###登録(**sing up**)
```vuejs:singup.vue
<template>
  <div>
    <h1>SING UP</h1>
    <div>
      <h3>E-mail</h3>
      <input type="text" placeholder="E-mail" v-model="email" />
    </div>
    <div>
      <h3>Password</h3>
      <input type="text" placeholder="Password" v-model="password" />
    </div>
    <button @click="createUserAccount">Sing UP!!</button>
  </div>
</template>

<script>
import firebase from "../firebase.js";
export default {
  name: "singup",
  data() {
    return {
      email: "",
      password: ""
    };
  }
};
</script>
```
###ログイン(**sing in**)
```vuejs:singin.vue
<template>
  <div>
    <h1>SING IN</h1>
    <div>
      <h3>E-mail</h3>
      <input type="text" placeholder="E-mail" v-model="email" />
    </div>
    <div>
      <h3>Password</h3>
      <input type="text" placeholder="Password" v-model="password" />
    </div>
    <button @click="userSingIn">Sing in Now!!</button>
  </div>
</template>

<script>
import firebase from "../firebase.js";
export default {
  name: "singin",
  data() {
    return {
      email: "",
      password: ""
    };
  }
};
</script>
```
###ログアウト(**sing out**)
```vuejs:singout.vue
<template>
  <div>
    <h1>SING OUT</h1>
    <button @click="singout">Sing out Now!!</button>
  </div>
</template>

<script>
import firebase from "../firebase.js";
export default {
  name: "singout"
};
</script>
```
###認証専用ページ
```vuejs:mypage.vue
<template>
  <div>
    <h1>SING OUT</h1>
    <p>My Page!!<p>
  </div>
</template>

<script>
export default {
  name: "mypage"
};
</script>
```

##ルーティング
次に作成したコンポーネントを合わせてルートレコードを設定していきましょう
###ルートファイルを設定
```javascript:router.js
import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";
/*ここから新規追加*/
import Singup from "../components/singup.vue";
import Singin from "../components/singin.vue";
import Singout from "../components/singout.vue";
import Mypage from "../components/mypage.vue";
/*ここまで*/

Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    name: "home",
    component: Home
  },
  {
    path: "/about",
    name: "About",
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () =>
      import(/* webpackChunkName: "about" */ "../views/About.vue")
  },
  /*ここから新規追加*/
  {
    path: "/singup",
    name: "singup",
    component: Singup
  },
  {
    path: "/singin",
    name: "singin",
    component: Singin
  },
  {
    path: "/singout",
    name: "singout",
    component: Singout
  },
  {
    path: "/mypage",
    name: "mypage",
    component: Mypage
  }
  /*ここまで*/
];

const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes
});

export default router;
```
先程作成したコンポーネントを`router.js`に追加し、ルーティング設定しました。
次に、`App.vue`の中に作ったコンポーネントへ遷移するためのリンクを設定しましょう。

```html:App.vue
<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link> |
      /*ここから新規追加*/
      <router-link to="/singup">Sing up</router-link> |
      <router-link to="/singin">Sing in</router-link> |
      <router-link to="/singout">Sing out</router-link> |
      <router-link to="/mypage">Mypage</router-link> |
      /*ここまで*/
    </div>
    <router-view />
  </div>
</template>
```

###ここまでのを確認
`yarn serve`を行い、一旦ここまでの作成段階のを確認しましょう。
![e0668afb493addf7c04efaa1a61ce9c7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257615/cb82730c-d58c-3fe9-e426-780d1fd5751c.png)
**Sing up**から**Mypage**までのリンクを押し、無事ページが遷移したら成功です。

#Firebaseを使い認証
##FirebaseでE-mailとPasswordで登録できるように設定しよう

認証などの処理を行う前に、**Firebase Console**画面で、**Authentication**の設定を行いましょう。

**Authentication**のログイン方法で、**メール/パスワード**を有効化します。
![8ff1cf5ec77c4e7b587ca1546b1a069e.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257615/a1ca861a-d859-8255-8c51-2754117a4115.png)

![02d64008938d5b32fbc3ef791a6c133f.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257615/f9743ea9-1282-f641-23eb-5e03972e0017.png)

メールリンクは今回は有効化しなくてもOKです。
これで**メール/パスワード**を用いてユーザー登録を行う準備が完了しました。

##Firebaseにアカウントを登録しよう
先程作った`Singup`コンポーネントに、ユーザーアカウントを追加する処理を組み込みましょう。
[Firebaseのユーザーを管理する](https://firebase.google.com/docs/auth/web/manage-users?hl=ja#create_a_user)

```vuejs:singup.vue
<script>
  methods: {
    createUserAccount() {
      firebase
        .auth()
        .createUserWithEmailAndPassword(this.email, this.password)
        .then(() => {
          alert("Create Account");
        })
        .catch(error => {
          alert("Error!", error.message);
          console.error("Account Regeister Error", error.message);
        });
    }
  }
</script>
```
`E-mail`と`Password`を用いてのよくあるユーザー登録を行うには、
`firebase.auth`の`createUserWithEmailAndPassword`メソッドを使用します。
第一引数に`E-mail`、第二引数に`Password`を渡してあげると、あとはよしなにやってくれます。
非同期で行うため、結果は`thenチェーン`内で行う必要があります。
あとは、このメソッドを`Button`コンポーネントより呼び出せば登録処理部分は完了です。

##ログイン処理をしよう
次はログインを行う処理を追加するために、作っておいた`Sing in`コンポーネントに処理を書き込んでいきましょう。

```vuejs:singin.vue
<script>
  methods: {
    userSingIn() {
      firebase
        .auth()
        .signInWithEmailAndPassword(this.email, this.password)
        .then(() => {
          alert("ログイン成功!");
          this.$router.push("/mypage");
        });
    }
  }
</script>
```
`firebase.auth`の`signInWithEmailAndPassword`メソッドを使い、ログイン処理を行います。
ユーザー登録時と同じ様に、引数にそろぞれ`E-mail`と`Password`を渡ししてあげればOKです。
無事ログイン処理が行えたら、`then`チェーンで内で`Router`の`push`メソッドを使い、ページを遷移させています。

###お試しにログインしてみる
先程作ったユーザ登録画面とログイン画面で、ユーザー登録を行えログインできるか試してみましょう。
ユーザ登録画面で登録が成功と`alert`が表示され、`Firebase Console`の認証関連のページで、ユーザーが登録されているはずです。
![b1a67ca936b76a15b9d4cc8d3fbc95cc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257615/0a4b85c5-63dd-f1b7-e2ff-c54d779dc3c1.png)

ログイン画面で、登録した`E-mail`と`Password`でログインができ、画面がMypageに遷移したらログイン画面も無事作成できています。
##ログアウト処理も作ってみる
ログインページが完了したので、次はログアウトページも作っておきましょう。

```vuejs:singout.vue
<script>
  methods: {
    singout() {
      firebase
        .auth()
        .signOut()
        .then(() => {
          alert("Logout!");
        })
        .catch(error => {
          alert(error);
        });
    }
  }
</script>
```
`firebase.auth`の`signOut`メソッドを使いログアウト処理を行っています。
ログアウト処理はこれだけで完了です。
ログアウト後にページトップに戻るような動きをつけたい場合には、
ログイン処理時に使った、

```
this.$router.push();
```
を使い、`push`の引数の中に遷移させたい先のルート名を渡しましょう。

##ページにログイン認証を組み込もう
ログイン/ログアウトできるようになりましたが、現状ではログイン時のみしか表示させたくない`Mypage`がログアウト時でも見ることができています。
次は、この`Mypage`をログイン時のみ表示するようにしましょう。
~~(ようやくここに来て本題に到達)~~

###認証が必要なことの状態をもたせよう
まず、`router.js`に認証処理を追加していきましょう。

```javascript:router.js
  {
    path: "/mypage",
    name: "mypage",
    component: Mypage,
    meta: { requiresAuth: true }
  }
```

まずは、`mypage`のルーティング部分に`meta`フィールドを追加します。
[Vue Router - ルートメタフィールド](https://router.vuejs.org/ja/guide/advanced/meta.html)
`meta`フィールドに`{ requiresAuth: true }`のオブジェクトをもたせて、このルートは認証が必要であることの状態をもたせます。
ただし、これだけでは状態を持っているだけなので、ナビゲーションガード、未ログインユーザーがこのページにアクセスしてもページが表示されてしまいます。
別途、ルートガードの処理を作る必要があります。

###ナビゲーションガードを実装しよう
[Vue Router - ナビゲーションガード](https://router.vuejs.org/ja/guide/advanced/navigation-guards.html)
公式にある、`router.beforeEach`を用いナビゲーションガードを実装してみましょう。
``

```javascript:router.js
const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes
});

router.beforeEach((to, from, next) => {
  const requiresAuth = to.matched.some(recode => recode.meta.requiresAuth);
  if (requiresAuth) {
    next({ path: "/singin", query: { redirect: to.fullPath } });
  } else {
    next();
  }
});

export default router;
```

`router.beforeEach`を`export default router`の前に追加しました。
`to.matched.some(recode => recode.meta.requiresAuth)`で、`requiresAuth`の状態をもつルートレコードなのかを確認しています。
もし、`requiresAuth`なら`next`を用いて`singin`へ、そうでないならユーザーが選択したルートレコードへ遷移します。
ただし、この状態ですと**ログイン**しているかどうかを確認していないので、`mypage`へアクセスすると誰でもログインページへ戻ってしまいます。
次は、Firebaseからログイン状態を取得してみましょう。

###Firebaseからログイン状態を取得
`router.beforeEach`の中にログイン状態を取得する方法もありますが、
今回は`firebase.js`に取得処理を書き、処理を分離化させる方法でやってみましょう。
`firebase.js`の中に書いていきましょう。

```javascript:firebase.js
firebase.getCurrentUser = () => {
  return new Promise((resolve, reject) => {
    const unsubscribe = firebase.auth().onAuthStateChanged(user => {
      unsubscribe()
      resolve(user);
    }, reject);
  });
};
```
[Firebase のユーザーを管理する](https://firebase.google.com/docs/auth/web/manage-users?hl=JA)
`onAuthStateChanged`を用い、ユーザーがログイン状態かを確認できます。
そして、このメソッドがログイン状態になったら即時にその情報を返します。

最後に先程の`router.beforeEach`にこのログイン状態かを確認する処理を追加しましょう。

```javascript:router.js
const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes
});

router.beforeEach(async (to, from, next) => {
  const requiresAuth = to.matched.some(recode => recode.meta.requiresAuth);
  if (requiresAuth && !(await firebase.getCurrentUser())) {
    next({ path: "/singin", query: { redirect: to.fullPath } });
  } else {
    next();
  }
});

export default router;
```

`await firebase.getCurrentUser()`は先程`firebase.js`に作成したメソッドとなります。
`firebase.getCurrentUser()`は`Promise`の非同期メソッドになるので、
`router.beforeEach`内の関数に`async`をつけ、これも非同期メソッドとして上げる必要があります。
そして、if文の条件に`firebase.getCurrentUser()`を追加しますがこれは`!`をつけ否定の条件にしておきます。

```javascript
if (requiresAuth && !(await firebase.getCurrentUser())
```

そして、2つをand条件にすることで
- **認証が必要なコンポーネント**
- **ログイン状態でではない**

を満たす場合のみに、ログインページへ遷移するようにしました。

###最後に無事動作するか確認
最後に、`yarn serve`を行い、ログインを行ってみてページ遷移するか、
ログアウト状態で**Mypage**へアクセスした場合に、ログイン画面へ戻るかを確認してみましょう。
無事動けば完成です！

#おわり
長くなりましたが、今回は**Firebase**を用いてVueでのログイン認証が必要なページの作り方を記事にしてみました。
Vueの認証が必要なページの作り方は、**Firebase**を使わなくても`router.beforeEach`の中で処理するのは共通になるので、一度覚えておけば他の認証サービスやAPIを使った場合でも応用は楽にできると思います。