Expo + ReactNativeで認証機能の実装を試してみる。本当はTwitter認証を実装したいんだけど、公式の情報がFacebook認証だったので、まずはFacebook認証を実装する。

プロジェクトを準備

まずは空っぽのプロジェクトを初期化する。テンプレートはblankを選択。

$ expo init ExpoFirebaseAuth

アプリケーションを起動してみる。

$ cd ExpoFirebaseAuth
$ yarn start

ここまではいつもどおりなので楽勝。

blankアプリケーション

FirebaseとFacebook側の設定

認証用にFacebookアプリを登録する。細かい手順は省く。

https://developers.facebook.com/apps/

設定 -> ベーシックで表示されているアプリIDとapp secretをメモっておく。

Facebookアプリ設定画面

Firebaseプロジェクトも登録しておく。 https://console.firebase.google.com/u/0/

Authentication -> Sign-in method画面でFacebookを有効にして、Facebookアプリから取得したアプリケーションIDとアプリシークレットを設定する。

Firebase側の設定

OAuthリダイレクトURIが表示されているので、これをFacebook側に設定する。

Facebookログイン -> 設定 -> 有効なOAuthリダイレクトURI

有効なOAuthリダイレクトURI設定

認証機能を実装する

公式の情報はここだが、若干わかりづらい。

https://docs.expo.io/guides/using-firebase/

この辺のエントリも参考にした。

まずは必要なパッケージをインストールする。yarnやnpmではなくexpo installを使うことでexpoに対応したバージョンを入れてくれるらしい。

最新のfirebaseパッケージでexpoを動かすとエラーになってしまうが、その辺を気にしなくて済む。

$ expo install firebase expo-facebook

App.tsx

import React, { Component } from 'react';
import * as Facebook from 'expo-facebook';
import { StyleSheet, Button, View } from 'react-native';
import firebaseApp from './firebase';
import * as firebase from 'firebase';

// Listen for authentication state to change.
firebaseApp.auth().onAuthStateChanged((user) => {
  console.log(user);
  if (user != null) {
    console.log("We are authenticated now!");
  }

  // Do other things
});


export default class App extends Component {
  async loginWithFacebook() {
    await Facebook.initializeAsync(
       '<FACEBOOK_APP_ID>',
    );
  
    const { type, token } = await Facebook.logInWithReadPermissionsAsync(
      { permissions: ['public_profile'] }
    );

    if (type === 'success') {
      // Build Firebase credential with the Facebook access token.
      const credential = firebase.auth.FacebookAuthProvider.credential(token);

      // Sign in with credential from the Facebook user.
      firebaseApp.auth().signInWithCredential(credential).catch((error) => {
        console.log(error)
        // Handle Errors here.
      });
    }
  }
  
  render() {
    return ( 
      <View style={styles.container}>
        <Button title="Sign in with Facebook" onPress={ this.loginWithFacebook } />
      </View>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

firebase.ts

import * as firebase from 'firebase';

// Initialize Firebase
const firebaseConfig = {
  apiKey: "<API_KEY>",
  authDomain: "<AUTH_DOMAIN>",
  databaseURL: "<DATABASE_URL>",
  projectId: "<PROJECT_ID>",
  storageBucket: "<STORAGE_BUCKET>",
  messagingSenderId: "<MESSAGING_SENDER_ID>",
  appId: "<APP_ID>"
};

const firebaseApp = firebase.initializeApp(firebaseConfig);
export default firebaseApp

解決済みだけどこのエラーにハマった。

Unhandled promise rejection: TypeError: undefined is not an object (evaluating '_firebase.default.auth.FacebookAuthProvider.credential')

下記によると、FacebookAuthProviderはstaticなメンバらしく、初期化されたfirebaseAppインスタンスを使用してfirebaseApp.auth.FacebookAuthProviderと記述しても動かない。

https://stackoverflow.com/questions/52203891/expo-firebase-auth-provider-is-undefined

そこをクリアしたらまた次のエラーにハマった。あちこち設定を変えてみても全然解決せずに困った。

Error: Unsuccessful debug_token response from Facebook: {"error":{"message":"(#100) The App_id in the input_token did not match the Viewing App","type":"OAuthException","code":100,"fbtrace_id":"ABE3G467m_GWeSVlBcrCBIj"}}

下記エントリのおかげでようやく原因がわかった。なんと、Facebook認証をしようとしたときに、自分が設定したFacebookアプリのAPP_IDではなく、ExpoクライアントアプリのAPP_IDが飛んでしまうそうな。

どないなっとんねん。Expo37からの変更点って書いてあるのでバージョン下げれば動くのかな。まだ試してない。

https://qrunch.net/@katsukiniwa/entries/SFko1lS4jGtcgEAS?ref=qrunch

これを回避するには、開発モードではなくstandaloneモードでビルドすれば良いらしい。

app.json

{
  "expo": {
    "name": "HelloWorld",
    "slug": "ExpoFirebaseAuth",
    "platforms": [
      "ios",
      "android",
      "web"
    ],
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "updates": {
      "fallbackToCacheTimeout": 0
    },
    "assetBundlePatterns": [
      "**/*"
    ],
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "com.expofirebase.auth",
      "buildNumber": "1.0.0"
    }
  }
}

ビルドしてみる。

$ expo build:ios

ぐぬぬ、何故かAppleの2段階認証が通らずに詰む。

Authentication with Apple Developer Portal failed!
Reason: Unknown reason, raw: "Unauthorized Access"

ちょっと時間がなくなってしまったので続きは後日やるか。気が向いたら追記する。