FirebaseUIによるログイン + Firestoreへのユーザ情報の書き込み

# 概要
FirebaseUIによるユーザ登録と、登録されたユーザ情報をFirestoreに書き込むという流れについてのメモです。

# FirebaseUI
FirebaseUIについてですが、
> FirebaseUI は Firebase Authentication SDK の上に構築されるライブラリで、アプリで使用するドロップイン UI フローを提供します。

とあるように、FirebaseUIを使用すれば、複数のプロバイダ（メールアドレス、Facebook、Twitterなどなど）によるユーザ登録、ログイン、ログイン後のページ遷移などのログインフローをほぼFirebaseUIに任せることができます。

# 方法
以下、方法を記していきますが、Firebaseプロジェクトの作成や、初期設定等の必要最低限のことは済んでいることが前提です。
[Firebase を JavaScript プロジェクトに追加する  |  Firebase](https://firebase.google.com/docs/web/setup?hl=ja)

## ログイン方法の設定
今回は利用者が多そうなFacebookログインを使いたいと思います。

### Facebook for Developersの設定
Facebookログイン機能を使うためには、「Facebook for Developers」で設定を行う必要があります。
[Facebook for Developers | 人と人がより身近になる世界を実現する](https://developers.facebook.com/?locale=ja_JP)

Facebook for Developersの「新しいアプリを作成」からアプリを作成し、
設定 > ベーシック
から画像のような、

* アプリID
* app secret

の二つをメモしておきます（Firebase Authenticationの設定で使用します）。
![facebook_config.png](https://qiita-image-store.s3.amazonaws.com/0/91285/1da28d30-df91-e8c3-d2b1-ef1e6d6965ac.png)


次に、Firebaseコンソールに戻り、下の画像のような、OAuthリダイレクトURI（黒く塗りつぶした部分）をコピーします。
![firebase_facebook.jpg](https://qiita-image-store.s3.amazonaws.com/0/91285/b26e45ce-557a-b840-315c-cdbd7eab278e.jpeg)


再びFacebook for Developersに戻り、
Facebookログイン > 設定
から「有効なOAuthリダイレクトURI」に、先ほどコピーしたURIを貼り付けます。
![facebook_login.png](https://qiita-image-store.s3.amazonaws.com/0/91285/3bc7d102-6cc7-f985-ed63-fddc745ee366.png)


### Firebase Authenticationの設定
Firebaseコンソールの「Authentication」から「ログイン方法」タブに切り替え、ログインプロバイダを選択します。「Facebook」を選択し、右側にある「有効にする」を切り替えて有効にします。
そして、先ほどのfacebook for developerの管理画面に記述されていた「アプリID」と「app secret」を、Firebase Authenticationの、

* アプリケーションID
* アプリシークレット

にペーストします。
![firebase_facebook.jpg](https://qiita-image-store.s3.amazonaws.com/0/91285/2f7649c8-3e47-af49-634f-2b0adb12e409.jpeg)


## FirebaseUIによるログイン処理
FirebaseUIをインストールします。

```
npm install firebaseui --save
```

以下がFirebaseUIによるログイン処理のコード全体です。
今回はReactで書いていますすが、おそらくもっとReactらしい記述方法があると思います...

```jsx
import firebase from 'firebase/app';
import firebaseui from 'firebaseui';
import 'firebaseui/dist/firebaseui.css';
import React from 'react';

export default class LoginPage extends React.Component {
  componentDidMount() {
    let ui = firebaseui.auth.AuthUI.getInstance();
    if (!ui) {
      ui = new firebaseui.auth.AuthUI(firebase.auth());
    }
    const uiConfig = {
      callbacks: {
        signInSuccessWithAuthResult: (authResult, redirectUrl) => {
          return true;
        },
        uiShown: () => {
          document.getElementById('loader').style.display = 'none';
        },
      },
      signInFlow: 'popup',
      signInSuccessUrl: 'mypage',
      signInOptions: [
        firebase.auth.FacebookAuthProvider.PROVIDER_ID,
      ],
      tosUrl: 'terms',
      privacyPolicyUrl: 'policy',
    };
    ui.start('#firebaseui-auth-container', uiConfig);
  }

  render() {
    return (
      <div>
        <div id="firebaseui-auth-container" />
        <div id="loader">Now Loading...</div>
      </div>
    );
  }
}
```

まず、FirebaseUI のログインフローを開始するには、

```js
const ui = new firebaseui.auth.AuthUI(firebase.auth());
```

として、基盤となる Auth インスタンスを渡して FirebaseUI インスタンスを初期化する必要があります。
ちなみに今回、

```js
let ui = firebaseui.auth.AuthUI.getInstance();
if (!ui) {
  ui = new firebaseui.auth.AuthUI(firebase.auth());
}
```

としているのは、SPAでサイトを構築した場合に`Error: An AuthUI instance already exists`となるのを防ぐ為です。

次に、`uiConfig`にFirebaseUIの設定（サポートするプロバイダ、UI のカスタマイズ、成功時のコールバックなど）を指定していきます。

`signInSuccessWithAuthResult`は、ユーザのサインイン成功時に自動的にリダイレクトを続行するか否かを記述します。

`uiShown`では、FirebaseUIのUIが描画されたときの処理を記述しています。ここでは、UIが表示されるまでは`<div id="loader">Now Loading...</div>`を表示させて、UIが表示されたら消す、という内容です。

`signInFlow`では、サインインフローをポップアップにするか、リダイレクトにするかなどが記述できます。

`signInSuccessUrl`は、サインイン処理完了後のリダイレクト先を記述します。
例えば、サインイン完了後に`/mypage`に遷移したいのであれば上記コードのように記述します。

`signInOptions`では、サインイン処理の為にユーザに提供したいプロバイダーを記述します。
今回はログインプロバイダーをFacebookに指定したので、上記コードのようになっています。
ちなみにTwitterを使いたいのであれば、

```js
firebase.auth.TwitterAuthProvider.PROVIDER_ID
```

のように記述します。

`tosUrl`では、利用規約のページを記述します。`signInSuccessUrl`と同じ要領です。

`privacyPolicyUrl`では、プライバシーポリシーのページを記述します。こちらも`signInSuccessUrl`と同じ要領です。
ちなみに`tosUrl`と`privacyPolicyUrl`の記述は必須になります。
`uiConfig`の設定項目の詳細は下記リンク先から確認できます。
[Configuration](https://github.com/firebase/firebaseui-web#configuration)

最後に、

```js
 ui.start('#firebaseui-auth-container', uiConfig);
```
としてUIをレンダリングします。


以上で画像のような、ユーザ登録、ログイン、ログイン後のページ遷移などをイイ感じにやってくれるUIが出来上がります。
![firebaseUI.png](https://qiita-image-store.s3.amazonaws.com/0/91285/c209311e-c982-02ac-6cca-089b8aaf2bd3.png)


## Cloud FunctionsによるFirestoreへのユーザ情報の書き込み
FirebaseUIによるユーザ登録、ログイン処理が実装できたので、次は登録されたユーザ情報をFirestoreに書き込む方法です。
これはCloud Functions経由で行います。
Cloud Functionsの設定方法は、下記リンク先が分かりやすいと思います。
[はじめに: 最初の関数を作成してデプロイする  |  Firebase](https://firebase.google.com/docs/functions/get-started?hl=ja)

とりあえずコード全体です。

```js
const functions = require('firebase-functions');
const admin = require('firebase-admin');

admin.initializeApp(functions.config().firebase);

exports.registerUsers = functions.auth.user().onCreate((user) => {
  const { uid } = user;
  const db = admin.firestore();
  const displayName = user.displayName || 'Anonymous';
  const email = user.email || '';
  const photoURL = user.photoURL || '/assets/img/default_profile.svg';

  return db.collection('users').doc(uid).set({
    user_name: displayName,
    photo_url: photoURL,
    email,
    create_on: new Date(),
  })
    .then(() => {
      console.log('Success'); // eslint-disable-line no-console
    })
    .catch((err) => {
      console.log(err); // eslint-disable-line no-console
    });
});

```

`functions.auth.user().onCreate()` イベント ハンドラを使用して、Firebase ユーザーが作成されたときにトリガーされる関数を作成できます。
そして、ユーザ属性にアクセスしてFirestoreに登録するユーザ情報として必要そうなものを取得します。今回の例では`displayName`, `email`, `photoURL`を取得しました。
下記リンク先にアクセス可能なユーザ属性一覧が載っています。
[Interface: UserRecord  |  Firebase](https://firebase.google.com/docs/reference/functions/functions.auth.UserRecord?hl=ja)

後は、`set()`メソッドを使用してFirestoreにユーザ情報を書き込みます。

以上で、FirebaseUIによるユーザ登録/ログイン処理と同時に、Firestoreにユーザ情報を書き込むことができます。

# 所感
FirebaseUIを使えば、面倒くさいユーザ登録やログイン処理をとても簡単に実現することができました。
加えて、Cloud Functionsともうまく連携すれば今回のようにできることの幅が広がりそうです。
また、FirebaseUIは日本語にも対応可能みたいなのでやってみたいと思います。

Firebase、とても便利です。

# 補足
仮に、Facebookログイン以外にもTwitterログインを使う場合、ほとんどはFacebookログインと同じような方法で実装することができます。
しかし、Cloud Functionsでユーザ情報を取得しようとしたときに、emailが取得できないことがあります。

```js
const email = user.email || ''; // Twitterの場合、emailが取得できない
```

そのようなときは、Twitter Developersの管理画面から画像のように「Request email address from users」にチェックを入れれば、emailが取得できるようになると思います。
![twitter_email.JPG](https://qiita-image-store.s3.amazonaws.com/0/91285/24cb202c-976f-427e-949f-b5e1e4df493e.jpeg)