Expo + Firebase Auth覚え書き

# はじめに
Expo + Firebaseで、下記のような一般的な構成のユーザー認証機能を実装してみたいと思います。

 * メールアドレス/パスワードでログイン
 * メールアドレス/パスワードでユーザー新規登録（初めての方）
 * Googleアカウントを使ってログイン
 * ログアウト

# 1. 基本設定
Firebaseのコンソール画面でWebアプリを追加し、スニペットをコピーして`firebaseConfig`をファイル化しておきます。Githubにアップされないように、今回は`.env`に記載したものを`react-native-dotenv`で読むこむようにします。

```text:.env
FIREBASE_API_KEY=<apiKey>
FIREBASE_AUTH_DOMAIN=<authDomain>
FIREBASE_DATABASE_URL=<databaseURL>
FIREBASE_PROJECT_ID=<projectId>
FIREBASE_STORAGE_BUCKET=<storageBucket>
FIREBASE_MESSAGING_SENDER_ID=<messagingSenderId>
FIREBASE_APP_ID=<appId>
FIREBASE_MEASUREMENT_ID=<measurementId>
```

```javascript:firebaseConfig.js
import {
  FIREBASE_API_KEY,
  FIREBASE_AUTH_DOMAIN,
  FIREBASE_DATABASE_URL,
  FIREBASE_PROJECT_ID,
  FIREBASE_STORAGE_BUCKET,
  FIREBASE_MESSAGING_SENDER_ID,
  FIREBASE_APP_ID,
  FIREBASE_MEASUREMENT_ID
} from 'react-native-dotenv';

export default {
  apiKey: FIREBASE_API_KEY,
  authDomain: FIREBASE_AUTH_DOMAIN,
  databaseURL: FIREBASE_DATABASE_URL,
  projectId: FIREBASE_PROJECT_ID,
  storageBucket: FIREBASE_STORAGE_BUCKET,
  messagingSenderId: FIREBASE_MESSAGING_SENDER_ID,
  appId: FIREBASE_APP_ID,
  measurementId: FIREBASE_MEASUREMENT_ID
};
```

これを使ってクライアント側で`firebase`モジュールを初期化し、
`auth()`メソッドで[Authクラス](https://firebase.google.com/docs/reference/js/firebase.auth.Auth)のインスタンスを取得します。

```javascript:firebase.js
import firebase from 'firebase';
import firebaseConfig from './firebaseConfig';

firebase.initializeApp(firebaseConfig);

export const auth = firebase.auth();
export default firebase;
```

`onAuthStateChanged`で認証状況が変化したらユーザー情報をstateに保持するようにしておきます。
シンプルなRedux実装に組み込むとしたらこのような感じになるかと思います。

```javascript:redux.js
auth.onAuthStateChanged((user) => {
  console.log('auth state changed', user);
  const current = store.getState().user.data;
  if (!current && user) {
    store.dispatch({
      type: 'SUCCESS_AUTH_USER',
      data: user
    });
  } else if (current && !user) {
    store.dispatch({
      type: 'SIGN_OUT_USER'
    });
  }
});
```

# 2. メールアドレス/パスワードで新規登録
Firebaseのコンソール画面で「開発」→「Authentication」→「ログイン方法」→「ログインプロパイダ」から「メール / パスワード」を有効にします。この画面で有効なログイン方法を設定することができます。
<img width="1435" alt="firebase.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514229/3c704f51-f35c-fb6b-ba89-f717c6f1e44e.png">

画面コンポーネント内、stateに`email`と`password`の値があり、ボタンを押したら`authUser`アクションをdipatchするとします。

```javascript:redux.js
export const authUser = (email, password) => (dispatch) => {
  dispatch({
    type: 'START_AUTH_USER'
  });
  auth.createUserWithEmailAndPassword(email, password)
    .catch(({ message }) => {
      dispatch({
        type: 'FAIL_AUTH_USER',
        message
      });
    });
};
```

`createUserWithEmailAndPassword`メソッドにメールアドレスとパスワードを渡すだけです。
これでユーザーが新規登録され、ユーザーオブジェクト（user）がstateにsetされます。
Firebaseのコンソール画面でユーザーが追加されているのがわかるはずです。

# 3. ログアウト

FirebaseAuthではデフォルトだと認証状態が保持されるような設定になっています（[詳しくはこちら](https://firebase.google.com/docs/auth/web/auth-state-persistence?hl=ja)）。
このままでは開発しにくいので、ログアウト機能を先に実装しておきたいと思います。ボタンを押したら`signOut`アクションをdispatchするとします。

```javascript:redux.js
export const signOut = () => (dispatch) => {
  dispatch({
    type: 'START_SIGN_OUT_USER'
  });
  auth.signOut()
    .catch(({ message}) => {
      Alert.alert(message);
    });
};
```

# 4. メールアドレス/パスワードでログイン

今度は先ほど作成したユーザーにログインしてみます。こちらも新規登録と同様に、

```javascript:redux.js
export const signIn = (email, password) => (dispatch) => {
  dispatch({
    type: 'START_AUTH_USER'
  });
  auth.signInWithEmailAndPassword(email, password)
    .catch(({ message}) => {
      dispatch({
        type: 'FAIL_AUTH_USER',
        message
      });
    });
};
```
ここまでは非常に簡単です。

# 5. Googleアカウントを使用してログイン
Googleログインについては2019年10月現在、2通りの方法がありますが、最新の方法はExpoClientでの開発に対応していませんので、ひとまず非推奨の旧式の方法を紹介します。

こちらの方法はFirebaseでは完結せず、Developpers Consoleでプロジェクトを作成し、認証情報を追加する必要があります。
「認証情報」タブ→「認証情報を作成」→「OAuthクライアントID」を選択しクライアントIDを作成します。Androidの場合は`パッケージ名`を、iOSの場合は`バンドルID`を`host.exp.exponent`と入力します。
<img width="787" alt="googleclientid.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514229/94cfd483-aaba-b190-0e21-01fca437fc73.png">

作成したクライアントIDはJSから使用するので、`.env`ファイルに追記しておきます。

```text
GOOGLE_AUTH_IOS_CLIENT_ID=<IOS_CLIENT_ID>
GOOGLE_AUTH_ANDROID_CLIENT_ID=<ANDROID_CLIENT_ID>
```
また、「OAuth同意画面」タブから最低限サポートメールアドレスを設定しておきましょう。これがないと認証エラーが発生します。
<img width="900" alt="google.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514229/2407a2eb-be81-1e5a-8e9d-25bd35f5095e.png">

ではアプリ側です。
認証に必要な`expo-google-app-auth`モジュールをインストールします。

```shell
$ yarn add expo-google-app-auth
```
`authGoogle`アクションをdispatchするとして、実装してみます。

```javascript:redux.js
import Google from 'expo-google-app-auth';

export const authGoogle = () => (dispatch) => {
  dispatch({
    type: 'START_AUTH_USER'
  });
  Google.logInAsync({
    behavior: 'web',
    iosClientId: GOOGLE_AUTH_IOS_CLIENT_ID,
    androidClientId: GOOGLE_AUTH_ANDROID_CLIENT_ID,
    scopes: ['profile', 'email']
  }).then((result) => {
    if (result.type === 'success') {
      const { idToken, accessToken } = result;
      const credential = firebase.auth.GoogleAuthProvider.credential(idToken, accessToken);
      auth.signInWithCredential(credential)
        .catch(({ message }) => {
          dispatch({
            type: 'FAIL_AUTH_USER',
            message
          });
        });
    } else {
      dispatch({
        type: 'FAIL_AUTH_USER',
        message: result.type
      });
    }
  }).catch(({ message }) => {
      dispatch({
        type: 'FAIL_AUTH_USER',
        message
      });
    });
};
```
実行すると、アプリ画面にはダイアログが表示された後Googleのログイン画面が表示されます。
これでGoogleアカウントと作成したAuthユーザー（既存ユーザーでも）が紐づけられます。

# Github

Authenticationを含め、メールアドレス・電話番号認証やFirestoreなども使用したサンプル
https://github.com/mildsummer/expo-auth-practice

# 参考
[Getting started with Firebase Authentication on React Native](https://medium.com/@chrisbianca/getting-started-with-firebase-authentication-on-react-native-a1ed3d2d6d91)
[Firebaseアプリをデプロイ後、Googleログインしようとすると403エラーがてたときの対処方法。](https://qiita.com/masakinihirota/items/e724dc39f1167674ecf3)