Expoで作成したReact Nativeプロジェクトの開発でFirebaseを使った認証を行いたい場合の設定方法について記述しています。firebaseの認証で利用する関数や処理の流れについてはReactの場合とほとんど同じです。

事前にExpoが利用できる環境があることを前提に進めています。macOSにおけるExpo環境の構築やReact Nativeの基礎を学習したい場合は公開済みの下記の文書を参考にしてください。

Firebaseでのプロジェクトの作成

Firebaseを利用するためにはFirebaseへのアカウント登録が必要があります。Googleアカウントを持っていれば簡単にアカウント登録を行うことができるのでアカウントの登録がまだの場合はアカウントの登録を行ってください。

アカウント登録が完了しhttps://firebase.google.com/にアクセスしコンソールに移動すると”Firebaseへようこそ”画面が表示されます。画面の中央付近にある”プロジェクトの作成”ボタンをクリックしてください。

Firebaseコンソール画面
Firebaseコンソール画面

プロジェクトの作成画面が表示されるのでプロジェクトの名前をつけます。ここではreact-native-authという名前をつけています。名前は任意なので好きな名前をつけてください。

プロジェクト名の設定
プロジェクト名の設定

Googleアナリティクスの設定画面が表示されますがテストの動作確認なのでアナリティクスを利用しないため無効に設定します。設定が完了したら”プロジェクトを作成”ボタンをクリックしてください。

Google Analyticsの設定
Google Analyticsの設定

プロジェクトの作成までにしばらく時間がかかります。

プロジェクトの作成中
プロジェクトの作成中

プロジェクトの作成が完了すると”新しいプロジェクトの準備ができました”が表示されます。”続行”ボタンをクリックしてください。

プロジェクトの作成完了画面
プロジェクトの作成完了画面

プロジェクト概要画面が表示されるのでユーザ認証と管理の”Authentication”をクリックします。

プロジェクト概要画面
プロジェクト概要画面

本文書ではメールアドレスとパスワードを利用してユーザ登録とログインを行うのでメール/パスワードをクリックしてください。その他Googleのアカウントなどを利用して認証を行うことができます。

認証方法の選択
認証方法の選択

メール/パスワードを有効にするに変更にして”保存”ボタンをクリックしてください。

メールアドレスの有効化
メールアドレスの有効化

認証の設定は完了です。

Firebaseを利用するにはアプリの追加が必要になるのでコンソール画面に戻って画面中央にある</>ボタンをクリックしてください。アプリを利用するためのコードを取得します。

プロジェクト概要画面
プロジェクト概要画面

アプリの名前をつけてください。任意の名前のアプリの名前を入力してアプリを登録ボタンをクリックしてください。

ウェブアプリへのFirebaseの追加
ウェブアプリへのFirebaseの追加

表示される情報は登録したアプリ固有の情報です。Firebaseに接続するために必要となる情報が表示されているので大切に保管してください。後ほどこれらの情報をReact NativeからFirebaseへの接続に利用します。

Firebase SDKの表示
Firebase SDKの表示

Expoによるプロジェクトの作成

Expoを使ってReact Nativeプロジェクトを作成するためにexpo initコマンドを実行します。テンプレートの選択が必要なのでtemplateはblankを選択して実行します。


 % expo init react-native-auth

React Nativeのプロジェクトの作成が完了したらfirebaseのライブラリのインストールを行います。expoコマンドを実行します。


 % cd react-native-auth
 % expo install firebase

プロジェクトフォルダにfirebase.jsファイルを作成してfirebaseの管理画面上のFirebase SDKの追加で表示されていた接続情報をコピー&ペースとします。下記を利用してもFirebaseに接続することはできないので各自のアカウントで取得した内容を利用してください。


import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';

const firebaseConfig = {
  apiKey: 'AIzaSyDKaYMaWY909zVnsYZVogjL7UK3EpVprGs',
  authDomain: 'react-native-auth-ecd.firebaseapp.com',
  projectId: 'react-native-auth-ecd',
  storageBucket: 'react-native-auth-ecd.appspot.com',
  messagingSenderId: '377348076634',
  appId: '1:3773788066634:web:d2984df21020a0bcbb85b3',
};

const app = initializeApp(firebaseConfig);

export const auth = getAuth(app);

firebaseの認証機能を利用するのでfirebase/authからgetAuthをimportしてauthをexportしています。

React Navigationの設定

画面間の移動ができるようにReact Navigationのインストールを行います。React Navigationをこれまで利用したことがない場合は下記の文書が参考になります。

React Navigationを利用するために必要となるライブラリのインストールを行います。


 % npm install @react-navigation/native

expoコマンドを利用してreact-native-screens, react-native-safe-area-contextをインストールします。


% expo install react-native-screens react-native-safe-area-context

NavigatorにNative Stack Navigatorを利用するのでreact-navigation/native-stackライブラリをインストールします。


% npm install @react-navigation/native-stack
その他のNavigatorにStack ,Tabs, DrawerなどのNavigatorがあります。それらを利用したい場合は新たに別のライブラリをインストールする必要があります。

画面の作成

画面の作成を行うためプロジェクトフォルダにscreensフォルダを作成してその中にHomeScreen.js, RegisterScreen.js, LoginScreen.jsファイルを作成していきます。3つの画面のみ作成しますがHomeScreenは認証が完了しているユーザのみ表示されます。RegisterScreenとLoginScreenは認証が完了していないユーザの場合に表示される画面です。

Home画面の作成

HomeScreen.jsファイルは下記の通りに記述します。ユーザ登録、ログインが完了直後に表示される画面です。通常はこのファイルの中に認証が完了したユーザのみ閲覧が可能な情報を表示させます。


import React from 'react';
import { View, Text } from 'react-native';

const HomeScreen = () => {
  return (
    <View>
      <Text>ホーム画面</Text>
    </View>
  );
};

export default HomeScreen;

ユーザ登録画面の作成

ユーザ登録画面のRegisterScreen.jsファイルには入力フォームを追加します。

入力フォームを画面中央に表示させ、スマホの場合画面上に表示されるキーボードが入力項目、ボタンに重ならないようにViewではなくKeyboardAvoidingViewコンポーネントを利用します。


import React from 'react';
import {
  View,
  TextInput,
  Text,
  TouchableOpacity,
  KeyboardAvoidingView,
} from 'react-native';

const RegisterScreen = () => {

  return (
    <KeyboardAvoidingView
      behavior="padding"
      style={{
        justifyContent: 'center',
        alignItems: 'center',
        flex: 1,
      }}
    >
      //フォーム設定場所
    </KeyboardAvoidingView>
  );
};

export default RegisterScreen;

画面上で入力を行うための要素を作成するためにTextInputコンポーネントを利用します。KeyboardAvoidingViewタグの中に2つのTextInputコンポーネントと登録ボタンを作成するためにTouchableOpacityコンポーネントを設定します。メールアドレス用とパスワード用のTextInputを追加しています。


const RegisterScreen = () => {

  return (
    <KeyboardAvoidingView
      behavior="padding"
      style={{
        justifyContent: 'center',
        alignItems: 'center',
        flex: 1,
      }}
    >
      <Text style={{ fontSize: 20, marginBottom: 20 }}>ユーザ登録画面</Text>
      <View style={{ marginBottom: 20 }}>
        <TextInput
          style={{
            width: 250,
            borderWidth: 1,
            padding: 5,
            borderColor: 'gray',
          }}
          autoCapitalize="none"
          autoCorrect={false}
        />
      </View>
      <View style={{ marginBottom: 20 }}>
        <TextInput
          style={{
            width: 250,
            borderWidth: 1,
            padding: 5,
            borderColor: 'gray',
          }}
          placeholder="パスワードを入力してください"
          secureTextEntry={true}
          autoCapitalize="none"
        />
      </View>
      <TouchableOpacity
        style={{
          padding: 10,
          backgroundColor: '#88cb7f',
          borderRadius: 10,
        }}
      >
        <Text style={{ color: 'white' }}>登録する</Text>
      </TouchableOpacity>
    </KeyboardAvoidingView>
  );
};

export default RegisterScreen;

TextInputで設定しているautoCapicalizeはアルファベットを入力した際に先頭の文字が自動で大文字に変換されることを停止します。autoCorrectは文字列入力後に表示される候補の表示を停止させます。表示される候補によって先頭の文字を自動で大文字する等が行われます。パスワードについてはsecureTextEntryを設定することで入力した文字が●で表示されるように設定を行っています。

iOSで実際に動作確認を行うと入力フォームとキーボードが重なることなく以下のように表示されます。KeyboardAvoidingViewコンポーネントを利用していない場合は登録するボタンが表示されるキーボードで見えなくなります。

RegisterScreen画面をiOS実機で確認
RegisterScreen画面をiOS実機で確認

入力した文字列を保持できるようにuseState Hookの設定を行います。各TextInputにはonChangeTextイベントとvalueを追加設定しています。


import React, { useState } from 'react';
//
const RegisterScreen = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  return (
    <KeyboardAvoidingView
      behavior="padding"
      style={{
        justifyContent: 'center',
        alignItems: 'center',
        flex: 1,
      }}
    >
      <Text style={{ fontSize: 20, marginBottom: 20 }}>ユーザ登録画面</Text>
      <View style={{ marginBottom: 20 }}>
        <TextInput
          style={{
            width: 250,
            borderWidth: 1,
            padding: 5,
            borderColor: 'gray',
          }}
          onChangeText={setEmail}
          value={email}
          placeholder="メールアドレスを入力してください"
          autoCapitalize="none"
          autoCorrect={false}
        />
      </View>
      <View style={{ marginBottom: 20 }}>
        <TextInput
          style={{
            width: 250,
            borderWidth: 1,
            padding: 5,
            borderColor: 'gray',
          }}
          onChangeText={setPassword}
          value={password}
          placeholder="パスワードを入力してください"
          secureTextEntry={true}
          autoCapitalize="none"
        />
      </View>
      <TouchableOpacity
        style={{
          padding: 10,
          backgroundColor: '#88cb7f',
          borderRadius: 10,
        }}
      >
        <Text style={{ color: 'white' }}>登録する</Text>
      </TouchableOpacity>
    </KeyboardAvoidingView>
  );
};

入力した値をfirebaseに保存できるように作成済みのfirebase.jsファイルからauthをimportして、firebase/authからcreateUserWithEmailAndPasswordをimportしてTouchableOpacityコンポーネントで作成したボタンにonPressイベントを設定します。onPressイベントにはhandleRegister関数を指定します。


<TouchableOpacity
  style={{
    padding: 10,
    backgroundColor: '#88cb7f',
    borderRadius: 10,
  }}
  onPress={handleRegister}
  // disabled={!email || !password}
>
  <Text style={{ color: 'white' }}>登録する</Text>
</TouchableOpacity>

handleRegister関数内でcreateUserWithEmailAndPasswordと入力したemailとpasswordを利用します。


import { createUserWithEmailAndPassword } from 'firebase/auth';
import { auth } from '../firebase';
//略

const handleRegister = async () => {
  try {
    const user = await createUserWithEmailAndPassword(auth, email, password);
    console.log(user);
  } catch (error) {
    console.log(error.message);
  }
};

入力フォームからemailとパスワードを入力して登録するボタンをタッチするとfirebaseの管理画面上に入力したユーザが登録されます。

登録されたユーザの確認
登録されたユーザの確認

ここまでの設定でReact NaviteからFirebaseのユーザ登録までの処理の流れを確認することができました。

ユーザのログイン状態の監視

firebaseではユーザのログイン、ログアウトをonAuthStateChangedを利用して監視することでfirebaseにログインしているユーザの情報を取得することができます。App.jsのuseEffect Hookの中でonAuthStateChangedで監視を開始します。onAuthStateChangedを実行すると監視を解除するための関数unsubscribeが戻されるのでuseEffectのクリーンアップでonAuthStateChangedの解除を行っています。


import React, { useEffect } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './screens/HomeScreen';
import RegisterScreen from './screens/RegisterScreen';
import { onAuthStateChanged } from 'firebase/auth';
import { auth } from './firebase';

const Stack = createNativeStackNavigator();

export default function App() {
  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      if (user) {
        console.log(user);
      }
    });
    return () => unsubscribe();
  }, []);
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Register" component={RegisterScreen} />
        <Stack.Screen name="Home" component={HomeScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

createUserWithEmailAndPasswordを実行してユーザの登録が完了してfirebaseのログアウト処理が行われていなければコンソールにユーザ情報が表示されます。ログアウト処理は実装していないので本文書に沿って実行していればログインしている状態なのでユーザ情報が表示されます。

コンソールにユーザ情報が表示されている場合はログインしている状態なのでユーザ登録、ログインしていない場合にはHome画面が表示されるように条件分岐を追加します。条件分岐にはユーザ情報を利用します。

ユーザ情報を保存するためにuseStateでuserを定義します。


import React, { useEffect, useState } from 'react';
//略
const [user, setUser] = useState('');

onAuthStateChangedを使ってログインしている場合にsetUserを利用してユーザ情報を保存します。ログインしていない場合にはuser情報をblankにします。


useEffect(() => {
  const unsubscribe = onAuthStateChanged(auth, (user) => {
    if (user) {
      console.log(user);
      setUser(user);
    } else {
      setUser('');
    }
  });
  return () => unsubscribe();
}, []);

userにユーザ情報が保存されているかどうかの条件分岐を利用してログインしている場合はHome画面、ログインしていない場合はユーザ登録画面が表示されるように設定を行います。


<NavigationContainer>
  <Stack.Navigator>
    {user ? (
      <Stack.Screen name="Home" component={HomeScreen} />
    ) : (
      <Stack.Screen name="Register" component={RegisterScreen} />
    )}
  </Stack.Navigator>
</NavigationContainer>

シュミレーターで確認するとログインしている場合はホーム画面が表示されます。

ホーム画面
ホーム画面

画面をリロードすると一度ユーザ登録画面が表示されてからホーム画面へと移動が行われます。理由はuserの初期値は”なのでonAuthStateChangedでユーザ情報を取得する前に分岐処理が実行され、ユーザ登録画面が表示された後onAuthStateChanged処理が完了してユーザ情報がuserに保存されホーム画面が表示されるためです。

アプリケーションを起動しているコンソールで”r”を打てばリロードが行われません。シュミレーターの画面上でリロードを行いたいばああいは”ctrl + d” + “cmd + d” で行うことができます。

ユーザ登録画面を表示しないようにuseStateでloading変数を追加してonAuthStateChanged処理が完了するまでは画面に”loading…”の文字が表示されるように設定します。デフォルトではloadingの値はtrueに設定しonAuthStateChangedが完了すると値をfalseに設定します。loading画面の表示はloadingを利用して分岐させています。


import React, { useEffect, useState } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './screens/HomeScreen';
import RegisterScreen from './screens/RegisterScreen';
import { onAuthStateChanged } from 'firebase/auth';
import { auth } from './firebase';
import { View, Text } from 'react-native';

const Stack = createNativeStackNavigator();

export default function App() {
  const [user, setUser] = useState('');
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      setLoading(false);
      if (user) {
        console.log(user);
        setUser(user);
      } else {
        setUser('');
      }
    });
    return () => unsubscribe();
  }, []);

  if (loading) {
    return (
      <View
        style={{
          justifyContent: 'center',
          alignItems: 'center',
          flex: 1,
        }}
      >
        <Text>Loading...</Text>
      </View>
    );
  } else {
    return (
      <NavigationContainer>
        <Stack.Navigator>
          {user ? (
            <Stack.Screen name="Home" component={HomeScreen} />
          ) : (
            <Stack.Screen name="Register" component={RegisterScreen} />
          )}
        </Stack.Navigator>
      </NavigationContainer>
    );
  }
}

設定を行いリロードを行うと一瞬画面の真ん中にLoading…が表示されホーム画面が表示されるようになります。

ログインしている場合にホーム画面が表示されることがわかったのでログアウト処理を追加してログインしていない場合にはユーザ登録画面が表示されるか確認します。

ログアウト処理

ホーム画面にログアウトボタンを追加してタッチするとログアウトできるように設定を行います。ボタンにはonPressイベントでボタンをタッチするhandleLogoutが実行されます。handleLogoutが実行されるとコンソールにlogoutが表示されます。


import React from 'react';
import { View, Text, TouchableOpacity } from 'react-native';

const HomeScreen = () => {
  const handleLogout = () => {
    console.log('logout');
  };
  return (
    <View>
      <Text>ホーム画面</Text>
      <TouchableOpacity
        onPress={handleLogout}
        style={{
          marginTop: 10,
          padding: 10,
          backgroundColor: '#88cb7f',
          borderRadius: 10,
          width: 100,
        }}
      >
        <Text style={{ color: 'white' }}>ログアウト</Text>
      </TouchableOpacity>
    </View>
  );
};

export default HomeScreen;

シュミレーターで確認するとホーム画面の文字の下にログアウトボタンが表示されます。ボタンをタッチするとコンソールに”logout”が表示されることを確認してください。

ログアウトボタン表示
ログアウトボタン表示

handleLogout関数の中でfirebaseのsignOut関数を実行します。signOut関数を実行することでfirebaseからログアウトすることができます。


const handleLogout = () => {
  signOut(auth)
    .then(() => {
      console.log('logout');
    })
    .catch((error) => {
      console.log(error.message);
    });
};

ログアウトボタンをタッチするとhandleLogoutが実行され、signOutが行われることでfirebaseからのログアウトが行われApp.jsのonAuthStateChangedによりユーザ情報の値がblankになることでuserの値による分岐によってユーザ登録画面が表示されます。

onAuthStateChangedではApp.jsのuseEffectで開始されログイン、ログアウトを監視しているので状態の変化があるすぐにその変更を検知してonAuthStateChanged内の処理を実行します。

ログイン画面の作成

ログアウト処理後にユーザ登録画面ではなくログイン画面が表示されるようにユーザ登録画面を元にログイン画面の作成を行います。screensフォルダにLoginScreen.jsファイルを作成します。

2つの画面の大きな違いはユーザ登録の場合は入力フォームを入力後にcreateUserWithEmailAndPassword関数を実行していましたがログインの場合はsignInWithEmailAndPasswordを実行することです。関数名は異なりますが引数は同じです。記述している内容/処理はほとんど同じで違いは画面に表示される文言などです。


import React, { useState } from 'react';
import { signInWithEmailAndPassword } from 'firebase/auth';
import {
  View,
  TextInput,
  Text,
  TouchableOpacity,
  KeyboardAvoidingView,
} from 'react-native';
import { auth } from '../firebase';

const LoginScreen = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleLogin = async () => {
    try {
      await signInWithEmailAndPassword(auth, email, password);
    } catch (error) {
      console.log(error.message);
    }
  };

  return (
    <KeyboardAvoidingView
      behavior="padding"
      style={{
        justifyContent: 'center',
        alignItems: 'center',
        flex: 1,
      }}
    >
      <Text style={{ fontSize: 20, marginBottom: 20 }}>ログイン画面</Text>
      <View style={{ marginBottom: 20 }}>
        <TextInput
          style={{
            width: 250,
            borderWidth: 1,
            padding: 5,
            borderColor: 'gray',
          }}
          onChangeText={setEmail}
          value={email}
          placeholder="メールアドレスを入力してください"
          autoCapitalize="none"
          autoCorrect={false}
        />
      </View>
      <View style={{ marginBottom: 20 }}>
        <TextInput
          style={{
            width: 250,
            borderWidth: 1,
            padding: 5,
            borderColor: 'gray',
          }}
          onChangeText={setPassword}
          value={password}
          placeholder="パスワードを入力してください"
          secureTextEntry={true}
          autoCapitalize="none"
        />
      </View>
      <TouchableOpacity
        style={{
          padding: 10,
          backgroundColor: '#88cb7f',
          borderRadius: 10,
        }}
        onPress={handleLogin}
        // disabled={!email || !password}
      >
        <Text style={{ color: 'white' }}>ログイン</Text>
      </TouchableOpacity>
    </KeyboardAvoidingView>
  );
};

export default LoginScreen;

作成したLoginScreen画面を表示させるためにはApp.jsにimportする必要があります。


<NavigationContainer>
  <Stack.Navigator>
    {user ? (
      <Stack.Screen name="Home" component={HomeScreen} />
    ) : (
      <>
        <Stack.Screen name="Login" component={LoginScreen} />
        <Stack.Screen name="Register" component={RegisterScreen} />
      </>
    )}
  </Stack.Navigator>
</NavigationContainer>

ユーザがログインしている場合はホーム画面、ログインしていない場合にはログイン画面が表示されるようになります。ホーム画面が表示された場合はログアウトボタンをタッチしてログアウトを行ってください。

ログイン画面
ログイン画面

ログイン画面の入力フォームに正しいメールアドレスとパスワードを入力してログインボタンをタッチするとホーム画面が表示されます。

ユーザを登録していない人はログインすることができないのでログイン画面からユーザ登録画面に移動できるようにログインボタンの下にTouchableOpacityコンポーネントのテキストを追加します。画面の移動にはpropsで渡されるnavigation.navigateを利用します。


<TouchableOpacity
  style={{
    padding: 10,
    backgroundColor: '#88cb7f',
    borderRadius: 10,
  }}
  onPress={handleLogin}
  // disabled={!email || !password}
>
  <Text style={{ color: 'white' }}>ログイン</Text>
</TouchableOpacity>
<TouchableOpacity
  style={{ marginTop: 10 }}
  onPress={() => navigation.navigate('Register')}
>
  <Text>ユーザ登録はこちら</Text>
</TouchableOpacity>

ログインボタンの下にテキストで”ユーザ登録はこちら”が表示されます。

画面移動が追加されたログイン画面
画面移動が追加されたログイン画面

”ユーザ登録はこちら”をタッチするとユーザ登録画面が表示されます。

ユーザ登録画面
ユーザ登録画面

React NativeとFirebaseを利用した認証機能を設定することができました。