Nuxt.jsでFirebaseUIを使ってログイン機能を作る

Nuxt.jsでFirebaseUIを使ってログイン機能を作ってみました。
ソースコードはこちらにあります(https://github.com/mk422756/nuxt-firebaseui-sample) 
# FirebaseUIとは？
FirebaseUI(https://github.com/firebase/firebaseui-web) はFirebase Authenticationを簡単に使用するために、Firebaseがあらかじめ用意してくれているUIです。

今回はNuxt.jsを使用してFirebaseUIのWeb版を組み込んでいきます。
![firebaseui.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29504/f2a8d220-aa1c-1bb9-2051-d82dec54ddfb.gif)

#セットアップ
### 必要なもの
- Nuxt.jsをインストールするためのNode.js環境
- Firebaseプロジェクトを作成するアカウント

### Firebaseのセットアップ
Firebaseのプロジェクトを作成します。
こちらの記事(https://blog.katsubemakito.net/firebase/firebase-make-newproject) を参考にしてプロジェクトを作成します。
プロジェクト名は適当で構いません。

次に、Authenticationで使用するプロバイダーを有効化します。
コンソールから [Authentication] > [ログイン方法] に移動し、各プロバイダの設定を行います。
とりあえず、メールアドレスとGoogle認証を有効化します。
Twitter等の外部IDプロバイダを設定しようとすると、TwitterAPIキー等が必要になります。
APIキーを取得するには申請が必要だったり面倒なので、今回は省略します。
<img width="1151" alt="Screen Shot 2019-11-24 at 11.45.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29504/875c14fa-019d-5e65-6e14-0758537e6d01.png">


### Nuxt.jsのセットアップ
Nuxt.jsと必要なライブラリをインストールしていきます。

```
npx create-nuxt-app nuxt-firebaseui-sample
```
Nuxt.jsのセットアップです。設定を色々聞かれますがデフォルトでOKです。
<img width="684" alt="Screen Shot 2019-11-23 at 18.24.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29504/24512b23-1d84-9884-2eec-cfdbbbd10af2.png">

セットアップが完了したら、次はライブラリをインストールしていきます。

```bash

cd nuxt-firebaseui-sample
npm install --save firebase firebaseui
npm install --save-dev @nuxtjs/dotenv
```

firebaseとfirebaseuiは認証に必要です。
@nuxtjs/dotenvは.env設定ファイルから環境変数を設定するために使用します。APIキーなどの設定値をGitで管理したくない場合に便利です。

dotenvを使用するために、nuxt.config.jsに以下の設定を追加します。

```javascript

export default {
  buildModules: ['@nuxtjs/dotenv'],
};
```

次にプロジェクトのルートに.envを作成し、Firebaseの設定を記述します。
Firebaseの設定値は、作成したFirebaseプロジェクトの [プロジェクトの設定] > [アプリの追加] でアプリを追加すると表示されます。

```
FB_API_KEY=AIzaxxxx-xxxxxxxxxxxxxxxx
FB_AUTH_DOMAIN=uitest-xxxxx.firebaseapp.com
FB_DATABASE_URL=https://uitest-xxxxx.firebaseio.com
FB_PROJECT_ID=uitest-xxxxx
FB_STORAGE_BUCKET=uitest-xxxxx.appspot.com
FB_MESSAGE_SENDER_ID=3000000000
FB_APPID=1:300000000:web:671xxxxxxxxxxxxxx
```

#実装
###FirebaseUIの表示
今回は認証画面が表示されて、ログインすることができるだけのシンプルなページを作成します。
まず、components/Auth.vue を作成し、認証用のコンポーネントを作成します。

```vue
<template>
  <div>
    <div id="firebaseui-auth-container"></div>
  </div>
</template>
<script>
export default {
  mounted() {
    const firebase = require("firebase");
    const firebaseui = require("firebaseui");
    require("firebaseui/dist/firebaseui.css");

    const uiConfig = {
      signInSuccessUrl: "/",
      signInOptions: [
        firebaseui.auth.AnonymousAuthProvider.PROVIDER_ID,
        firebase.auth.EmailAuthProvider.PROVIDER_ID,
        firebase.auth.GoogleAuthProvider.PROVIDER_ID,
        firebase.auth.FacebookAuthProvider.PROVIDER_ID,
        firebase.auth.TwitterAuthProvider.PROVIDER_ID,
        firebase.auth.GithubAuthProvider.PROVIDER_ID,
        firebase.auth.PhoneAuthProvider.PROVIDER_ID
      ]
    };
    const firebaseConfig = {
      apiKey: process.env.FB_API_KEY,
      authDomain: process.env.FB_AUTH_DOMAIN,
      databaseURL: process.env.FB_DATABASE_URL,
      projectId: process.env.FB_PROJECT_ID,
      storageBucket: process.env.FB_STORAGE_BUCKET,
      messagingSenderId: process.env.FB_MESSAGE_SENDER_ID
    };
    firebase.initializeApp(firebaseConfig);

    const ui = new firebaseui.auth.AuthUI(firebase.auth());
    ui.start("#firebaseui-auth-container", uiConfig);
  }
};
</script>
```
mountedの中でFirebaseUI等をrequireしてるのは、FirebaseUIはSSRに対応していないからです。
SPAで動かすのならimportで大丈夫だと思います。
次に、Firebaseの初期化を行いFirebaseUIをスタートします。引数で指定したidのタグにFirebaseUIが表示されます。
Firebaseの初期化は.envで設定した設定値を使用します。
FirebaseUIのパラメータで使用するプロバイダを指定します。Twitter等は有効化していないためログイン等は失敗しますが、とりあえず表示させておきます。

次に、pages/index.vueからAuth.vueを使用します。

```vuejs
<template>
  <div>
    <div class="container">
      <div>
        <h1 class="title">FirebaseUI サンプル</h1>
        <auth />
      </div>
    </div>
  </div>
</template>

<script>
import Auth from "~/components/Auth.vue";

export default {
  components: {
    Auth
  }
};
</script>

<style>
.container {
  margin: 0 auto;
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
}

.title {
  font-family: "Quicksand", "Source Sans Pro", -apple-system, BlinkMacSystemFont,
    "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  display: block;
  font-weight: 300;
  font-size: 30px;
  color: #35495e;
  letter-spacing: 1px;
}
</style>

```

開発用サーバーを起動します。

```bash
npm run dev
```
これでFirebaseUIが表示され、新規登録とログインができるようになります。
<img width="311" alt="Screen Shot 2019-11-24 at 17.25.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29504/11028c34-7b35-2037-1edc-e2174de32b5c.png">

###Vuexで状態管理
ここまでで作成したもので、新規登録とログインはできるようになりました。
しかし、ログイン状態を管理していないため画面表示等ができません。
ですのでVuexを使用して状態を管理していきます。

まず、store/user.jsを作成し、ログインユーザーのメールアドレスと表示名を管理します。

```js
export const state = () => ({
  email: '',
  displayName: '',
});

export const actions = {
  login ({commit}, user) {
    commit ('setEmail', user.email);
    commit ('setDisplayName', user.displayName);
  },
  logout({commit}) {
    commit ('setEmail', '');
    commit ('setDisplayName', '');
  },
};

export const mutations = {
  setEmail (state, email) {
    state.email = email;
  },
  setDisplayName (state, displayName) {
    state.displayName = displayName;
  },
};

export const getters = {
  isLogin: state => {
    return !!state.email;
  },
  displayName: state => {
    return state.displayName;
  },
};

```
ログイン時と、ログアウト時のアクションを定義しています。
また、ログイン状態と表示名を返すゲッターも定義しています。

次に、components/Auth.vueから定義したアクションを使用します。

```vue
<template>
  <div>
    <div v-show="isLogin">
      <p class="login">ログイン中</p>
      <p class="name">名前：{{ displayName }}</p>
      <button @click="logout">ログアウト</button>
    </div>
    <div v-show="!isLogin" id="firebaseui-auth-container"></div>
  </div>
</template>
<script>
export default {
  computed: {
    isLogin() {
      return this.$store.getters["user/isLogin"];
    },
    displayName() {
      return this.$store.getters["user/displayName"];
    }
  },
  methods: {
    logout() {
      this.$store.dispatch("user/logout");
      const firebase = require("firebase");
      firebase
        .auth()
        .signOut()
        .then(() => {
          console.log("ログアウトしました");
        });
    }
  },
  mounted() {
    const firebase = require("firebase");
    const firebaseui = require("firebaseui");
    require("firebaseui/dist/firebaseui.css");

    const uiConfig = {
      signInSuccessUrl: "/",
      signInOptions: [
        firebaseui.auth.AnonymousAuthProvider.PROVIDER_ID,
        firebase.auth.EmailAuthProvider.PROVIDER_ID,
        firebase.auth.GoogleAuthProvider.PROVIDER_ID,
        firebase.auth.FacebookAuthProvider.PROVIDER_ID,
        firebase.auth.TwitterAuthProvider.PROVIDER_ID,
        firebase.auth.GithubAuthProvider.PROVIDER_ID,
        firebase.auth.PhoneAuthProvider.PROVIDER_ID
      ]
    };
    const firebaseConfig = {
      apiKey: process.env.FB_API_KEY,
      authDomain: process.env.FB_AUTH_DOMAIN,
      databaseURL: process.env.FB_DATABASE_URL,
      projectId: process.env.FB_PROJECT_ID,
      storageBucket: process.env.FB_STORAGE_BUCKET,
      messagingSenderId: process.env.FB_MESSAGE_SENDER_ID
    };
    firebase.initializeApp(firebaseConfig);

    const ui = new firebaseui.auth.AuthUI(firebase.auth());
    ui.start("#firebaseui-auth-container", uiConfig);

    firebase.auth().onAuthStateChanged(user => {
      this.$store.dispatch("user/login", user);
    });
  }
};
</script>
<style scoped>
.login {
  font-size: 1.25rem;
}
.name {
  font-size: 1.1rem;
}
</style>

```
firebase.auth().onAuthStateChangedを使用すると、ログイン状態の変更をトリガーにして処理が行われます。
これを使用して、ログイン状態をVuexに渡します。
また、ログイン中であればユーザーの表示名とログアウトボタンを表示します。
logoutメソッドでは、signOutメソッドを使用し、signOutに成功するとVuexの状態をリセットするアクションを呼び出しています。

これで、Vuexを使用してログイン状態を保持し、ログイン状態に応じて画面表示を切り替えることができました。
<img width="315" alt="Screen Shot 2019-11-24 at 18.04.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29504/f7ea6af0-86de-081a-abe7-f62289fcf73a.png">


###FirebaseUIの日本語化
各言語にローカライズするためには、自分でFirebaseUIをビルドする必要があります。
すいませんが、今回は省略します。。。
firebaseui-ja(https://www.npmjs.com/package/firebaseui-ja) というパッケージも存在しますが、バージョンの更新が止まっているので注意が必要です。


#所感
非常に簡単にログイン画面を作成できるのでとても便利だと思いました。
特にTwitter等のIDプロバイダとの連携を自前のUIで作成すると、IDプロパイダごとの処理とUIが必要なので、その手間が省けるのが非常に便利だと思いました。