ReactNative(非expo)版+Firebase auth+Sign in with Apple でユーザー認証を実装

この記事は[React Native Advent Calendar 2019](https://qiita.com/advent-calendar/2019/react-native)の24日目の記事です。

#はじめに
メリークリスマス! @mediaboxes です！
待ちに待ったクリスマスイブ24日！？実は先日失恋した僕にサンタさんがプレゼントで24日のAdventCalendarを前日(23日)にくれたので殴り書します！乱筆乱文失礼致します。それでも僕は元気です。

と、言うことで、僕の会社ではエンターテイメント系アプリからライフログ系アプリをReactNativeで数本開発と運用してきました。
もともとNativeでアプリを少人数で開発してきたのですが、iOSとAndroidの両方のソースコードやレイアウトを別々で作るのがバカらしくてReactNativeを採用しました。なので、Nativeのプロジェクトではない`expo init`より`react-native init`がメインが馴染み深いです。
なのでReactNative(非expo)版のFirebase authとSign in with Appleについてまとめます！

#Sign in with Appleについて（ざっくりだよ）
ソーシャルログインを使っているアプリの場合に対応が必要になります。リジェクトなど注意してください

##何ができるの？
* AppleIDでアプリやwebにログインできる
* 本メールアドレス隠して擬似メールアドレスでサービスに情報提供できる

##制約
* FacebookやGoogle、twitterのソーシャルログインを使ってるアプリは、Sign in with Appleを実装する必要がある 
* 2019年9月12日以降は新規リリースアプリはガイドラインに従う必要がある　※もう新しいアプリは対応が必要
* 2020年4月以降は既存アプリもガイドラインに従う必要がある　※既存のアプリは今後必要

##公式ガイドライン
ガイドラインの公式の詳しいお知らせはこちらをご確認ください
https://developer.apple.com/news/?id=09122019b

#実装
## 前提
iOS13以上のアプリのみ対応。
iOS13未満やAndroidは今回は非対応で、Sign in with Appleのボタンを表示しないで他のログインを選択してもらいます。WebviewなどでSign in with Apple JSを使う方法などあるそうですが、今回は未検証です。
ReactNative のバージョンはv0.615を使用
@react-native-firebase/app,@react-native-firebase/auth　モジュールはv6を使用

## 下準備
### Firebasesのプロジェクトを設定をSign In with Appleを有効にする
firebase上のアプリの設定は完了しているものとします。
![console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134620/abe40802-ed7d-c0d1-79db-a9a41e3234b4.png)
Firebases consoleのAuthentication/ログイン方法にあるAppleを有効にします。
※いつの間にかBetaの表記が消えましたね！

### AppleDeveloperの App ID ConfigurationでSign In with Appleを有効にする
![Apple Developer.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134620/7688e581-def5-0927-088e-78822b41c19e.png)
Sign In with Appleのチェックを入れるだけ

### XcodeのprojectのSign In with Appleを有効にする
![09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134620/beee694e-8fb2-dce1-c5e5-82998c13a50a.png)
Xcode11以上でSigning&Capabilitiesにある、+Capabolotyをクリックして表示された中にあるSign In with Appleをダブルクリックすると有効になる

###@react-native-firebas/appがプロジェクトにない場合はインストールする
https://invertase.io/oss/react-native-firebase/
ReactNativeでFirebaseを使う時によく使われるモジュール。
多くの機能をカバーしていますがバージョンの変化も早いのでご注意を。

react-native-firebaseのインストールは公式のこちらを参考に
https://invertase.io/oss/react-native-firebase/quick-start/

###@react-native-firebase/authがプロジェクトにない場合はインストールする
react-native-firebaseは多くの機能があるので、必要な機能ごとにモジュールをインストールする必要です。今回使用するfirebase authの用のモジュールのインストールは公式のこちらを参考に
https://invertase.io/oss/react-native-firebase/v6/auth/quick-start/

### @invertase/react-native-apple-authenticationをインストールする
https://github.com/invertase/react-native-apple-authentication

各ソーシャルログインで認証後にトークンをfirebaseに渡す(credentialを作成)と統合されたfirebaseIDが発行されます(signInWithCredential)。
このfirebaseIDを利用することで、facebook,twitter,googleを気にせずユーザー管理が行えます。

Sign In with Appleの認証を `@invertase/react-native-apple-authentication` で行います。
ボタン用のコンポーネントも準備されているのでそのまま使うましょう。

## ReactNativeの実装
@react-native-firebase/authが最近更新されて、Sign In with Appleの実装方法が記載されました。
https://invertase.io/oss/react-native-firebase/v6/auth/social-auth/
他に、Facebook、Twitter、Googleの実装方法が記載されていますので参考に。
今回は、iOS13未満やAndroidの為に、GoogleSigninも一緒に実装した例を参考に記載しておきます。

### 処理の流れ
onSignInGoogleで、google認証→credential作成
onSignInAppleで、apple認証→credential作成
↓
credentialでfirebase authにログイン(signInWithCredential)
↓
firebase.auth().currentUserを使って、自前のログイン処理を書く

```typescript:LoginScreen.tsxの一例
import * as React from "react";
import { View } from "react-native";
import { Container, Content, Text } from "native-base";

import { firebase } from "@react-native-firebase/auth";

import {
  GoogleSignin,
  GoogleSigninButton
} from "@react-native-community/google-signin";

import appleAuth, {
  AppleButton,
  AppleAuthRequestScope,
  AppleAuthRequestOperation
} from "@invertase/react-native-apple-authentication";

import { errorAlert } from "../utils";

export default class LoginScreen extends React.Component {
  //Google認証
  onSignInGoogle = async () => {
    try {
      let data;
      try {
        data = await GoogleSignin.signIn();
      } catch (error) {
        throw new Error("ログインに失敗しました");
      }
      const credential = firebase.auth.GoogleAuthProvider.credential(
        data.idToken,
        data.accessToken
      );
      await this.onSignIn(credential);
    } catch (error) {
      errorAlert(error.message);
    }
  };

  //Apple認証
  onSignInApple = async () => {
    try {
      let data;
      try {
        data = await appleAuth.performRequest({
          requestedOperation: AppleAuthRequestOperation.LOGIN,
          requestedScopes: [
            AppleAuthRequestScope.EMAIL,
            AppleAuthRequestScope.FULL_NAME
          ]
        });
      } catch (error) {
        throw new Error("ログインに失敗しました");
      }

      const credential = firebase.auth.AppleAuthProvider.credential(
        data.identityToken,
        data.nonce
      );
      await this.onSignIn(credential);
    } catch (error) {
      errorAlert(error.message);
    }
  };
   
  //signInWithCredential
  onSignIn = async credential => {
    try {
      const firebaseuser = await firebase
        .auth()
        .signInWithCredential(credential);

      const user = firebase.auth().currentUser;

      // ***********
      // user.uidを使ってアカウント処理
      // ***********

    } catch (error) {
      errorAlert(error.message);
    }
  };

  render() {
    return (
      <Container>
        <Content>
          <View>
            <View>
              <Text>ログイン</Text>

              <GoogleSigninButton
                size={GoogleSigninButton.Size.Wide}
                color={GoogleSigninButton.Color.Dark}
                onPress={this.onSignInGoogle}
              />

              {appleAuth.isSupported ? (
                <AppleButton
                  buttonStyle={AppleButton.Style.WHITE_OUTLINE}
                  buttonType={AppleButton.Type.SIGN_IN}
                  onPress={this.onSignInApple}
                />
              ) : null}
            </View>
          </View>
        </Content>
      </Container>
    );
  }
}
```

#まとめ
失恋をバネに初めての投稿させてもらいました。機会がありましたらまた投稿していきます。