ブログ

管理されたExpoワークフローReact NativeアプリケーションでGoogle OAuthを使用してFirebaseに認証する(Expo Goと互換性あり)

管理されたExpoワークフローReact NativeアプリケーションでGoogle OAuthを使用してFirebaseに認証する(Expo Goと互換性あり)

Typescriptを使用したReact Native ExpoアプリでExpo Goと互換性のあるGoogle OAuthログインでFirebase認証を設定するプロセスをご案内します。

執筆者

Dénes Gutai

(ChatGPTによる翻訳)

Published

AUG 23, 2023

Topics

#dev

Length

12 min read

Expo、Firebase、Googleサインイン。

2024 年のアップデート: OAuth を実装する場合は、Expo Go の使用を避けることをお勧めします。代わりにexpo-dev-clientを利用してください。これは類似した DX を提供しますが、ディープリンキングに関する制限はありません。

目次

<hr />

イントロと前提条件

この記事では、Typescript を使用した React Native Expo アプリで Expo Go と互換性のある Google OAuth ログインで Firebase 認証を設定するプロセスをご案内します。私たちは Expo と Firebase が提供するドキュメントの互換性がやや不完全であることを発見し、そのギャップを埋めるつもりです。

アップデート: この解決策は開発フェーズを容易にします。なぜなら、ネイティブコードをビルドするためにあなたやあなたのチームの環境を設定する必要がないからです。しかし、Expo SDK 49 から GoogleAuth プロバイダは非推奨になりました。 この変更に関する更なる情報は、Expo のガイダンスを参照してください。 もし本番環境の互換性に必要な変更にのみ関心がある場合、@react-native-google-signin/google-signinが Firebase とどのように連携するかを指摘した素晴らしいMedium の記事があります。

この記事のコードはこの github リポジトリで見つけることができます。

必要なパッケージ(バージョン付き)

"@expo/webpack-config": "^18.1.1",
"dotenv": "^16.3.1",
"expo": "~48.0.18",
"expo-auth-session": "^4.0.3",
"expo-secure-store": "~12.1.1",
"expo-web-browser": "^12.3.2",
"firebase": "^9.23.0"
<hr />

Google Cloud プロジェクトのセットアップ

プロジェクトの作成

まず、Google Cloud プロジェクトをセットアップします。

https://console.cloud.google.com/ にアクセスしてください。

Select Project ドロップダウンで New Project ボタンをクリックします。

フォームに記入します。

次に、https://console.cloud.google.com/apis/credentials に移動します。

これが初めてのプロジェクトの場合は、Configure Consent Screen をクリックして同意画面を設定する必要があります。

同意画面

これを外部ユーザーに設定するので、他の人もテスト可能になります。

必要なアプリ情報を設定します。

次に、ユーザーにアクセス許可を求めるスコープを追加する必要があります。

以上を選択後、これが表示されるはずです。

これ以降、アプリにログインできるテストユーザーを設定する必要があります。

OAuth クライアント

それでは、https://console.cloud.google.com/apis/credentials に戻って OAuth クライアントを作成しましょう。3 つの異なる OAuth クライアントが必要です。

  • 開発モード用(Web)
  • モバイルプラットフォーム用の追加 2 つ(Android、iOS)

画面の上部で + Create Credentials ボタンをクリックし、OAuth client ID を選択します。

Web クライアント

このクライアントは、開発モードで認証を処理する目的があります。これにより、Expo Go アプリケーションにとどまり、アプリケーションを毎回ベアワークフローに変更したり、事前にビルドする必要がありません。

アプリケーションタイプを Web Application に設定し、名前を付けます。

次に、オリジナルとリダイレクト URI を追加する必要があります。オリジナルは https://auth.expo.io です。リダイレクトは次の形式である必要があります。 https://auth.expo.io/@EXPO_CLI_USERNAME/PROJECT_SLUG

  • EXPO_CLI_USERNAMEは、Expo にログインするために使用するユーザー名です。
  • SLUGは、app.jsonまたはapp.config.(js|ts)で設定できるアプリケーションのスラッグです。

これにより、expo で使用する Google OAuth クライアント ID が生成されます。

Android

このクライアントは、Android プラットフォームの ID を提供します。これには、Android パッケージ名(アプリの設定でも見つかる)と、アプリの署名に使用される Android キーストアのフィンガープリントを追加する必要があります。

iOS

このクライアントは、iOS プラットフォームの ID を提供します。これには、Bunld 識別子(アプリの設定でも見つかります)を追加する必要があります。

<hr />

Firebase プロジェクトのセットアップ

https://console.firebase.google.com/ を開いて新しいプロジェクトを作成します。

注:プロジェクトにアナリティクスを含めないのは、設定するとベアワークフローに移行する必要があるためです。

これ以降、Firebase プロジェクトに認証を追加します。

Sign-in methods タブで、プロバイダとして Google を追加します。

それを有効にし、フォームに記入します。その後、この情報を保存します。

プロバイダを見ることができますが、Google Cloud Project の情報でそれを編集しましょう。

これらの 2 つの入力フィールドを、作成された Google Cloud Project の Client IDsecret に基づいて記入します。

最後に、Firebase プロジェクトの Project settings に移動します。

そして、Project ID と Web API キーをどこかに保存します。後で必要になります。

<hr />

dotenvを使用した Expo の追加設定

アプリが正常に機能するために、metro config を次のように変更する必要があります。ios.bundleIdentifierandroid.packageは、リリース部分で後で必要になります。

const { getDefaultConfig } = require('@expo/metro-config');

const defaultConfig = getDefaultConfig(__dirname);
defaultConfig.resolver.assetExts.push('cjs');

module.exports = defaultConfig;

app.json / app.config.(js|ts)を以下の内容に変更してください。

注意:scheme、name、slug プロパティは、以前 Google Cloud プロジェクトで設定したリダイレクト URI と一致する必要があります。

import { ExpoConfig } from 'expo/config';
import { config } from 'dotenv';
import path from 'path';

const env_file = path.join(__dirname, '.env');
const env = config({
  path: env_file,
});

if (env.error) {
  console.log('ENV FILE ERROR: ', env_file);
  throw env.error;
}

export const expoConfig: ExpoConfig = {
  scheme: 'scriptide-expo-firebase-demo',
  name: 'scriptide-expo-firebase-demo',
  slug: 'scriptide-expo-firebase-demo',
  version: '1.0.0',
  orientation: 'portrait',
  icon: './assets/images/icon.webp',
  userInterfaceStyle: 'light',
  splash: {
    image: './assets/images/splash.webp',
    resizeMode: 'contain',
    backgroundColor: '#ffffff',
  },

  assetBundlePatterns: ['**/*'],
  ios: {
    bundleIdentifier: 'scriptide.test.expo.firebase',
    supportsTablet: true,
  },
  android: {
    package: 'scriptide.test.expo.firebase',
    adaptiveIcon: {
      foregroundImage: './assets/adaptive-icon.webp',
      backgroundColor: '#ffffff',
    },

    intentFilters: [
      {
        action: 'VIEW',
        category: ['BROWSABLE', 'DEFAULT'],
      },
    ],
  },
  web: {
    favicon: './assets/favicon.webp',
  },
  extra: {
    ...env.parsed,
  },
};

export default expoConfig;
<hr />

認証ロジック

認証ロジックを処理するコンポーネントを作成します。これにより、ログインしたユーザーの名前とメールアドレスを表示するために、ユーザーを状態変数に保存します。

資格情報ファイル

まず、すべての資格情報を保存するファイルを作成します。これらはハードコードされるべきではないため、最初に .env ファイルを以下のように作成します。

# Information from the Firebase project
FIREBASE_API_KEY=AIza*****
FIREBASE_DOMAIN=scriptide-expo-firebase-demo.firebaseapp.com
FIREBASE_PROJECT_ID=scriptide-expo-firebase-demo


# Information from the Google Cloud Project's OAuth clients
EXPO_CLIENT_ID=560056473355-*******
ANDROID_CLIENT_ID=560056473355-*****
IOS_CLIENT_ID=560056473355-*****

その後、firebaseConfig.ts という名前のファイルを作成します。

import { initializeApp } from 'firebase/app';
import Constants from 'expo-constants';

const firebaseConfig = {
  apiKey: Constants.expoConfig?.extra?.FIREBASE_API_KEY ?? '',
  authDomain: Constants.expoConfig?.extra?.FIREBASE_DOMAIN ?? '',
  projectId: Constants.expoConfig?.extra?.FIREBASE_PROJECT_ID ?? '',
};

export const expoClientId = Constants.expoConfig?.extra?.EXPO_CLIENT_ID;
export const iosClientId = Constants.expoConfig?.extra?.ANDROID_CLIENT_ID;
export const androidClientId = Constants.expoConfig?.extra?.IOS_CLIENT_ID;

export const app = initializeApp(firebaseConfig);

Google プロバイダからid_tokenを取得

認証フローで最初に行うことは、Google OAuth プロバイダの id_token を取得することです。これには、useIdTokenAuthRequestというフックを使用します。

まず、それをインポートする必要があります。しかし、他のプロバイダのための余地を持たせるため、以下のようにインポートしましょう。

import { useIdTokenAuthRequest as useGoogleIdTokenAuthRequest } from 'expo-auth-session/providers/google';

そして、コンポーネントで以下のように使用します。

// Google OAuthプロバイダを認証するための関数を提供するフック
const [, googleResponse, promptAsyncGoogle] = useGoogleIdTokenAuthRequest({
  selectAccount: true,
  expoClientId,
  iosClientId,
  androidClientId,
});

このフックからアクセスできる 2 つの変数は以下の通りです。

  • promptAsyncGoogle は Google ログインをトリガーする関数です
  • googleResponse は Google ログインイベントのレスポンスを格納するものです

ユーザーが Google ログインボタンをクリックした場合に promptAsyncGoogle() を呼び出しましょう:

// Googleプロバイダを通じてログインを処理する

const handleLoginGoogle = async () => {
  await promptAsyncGoogle();
};
<Button title={'Login'} onPress={handleLoginGoogle} />

ボタンをクリックすると Google のプロンプトが表示され、ユーザーがログインすると最終的に googleResponse 変数が更新されます。

しかし、このログインはプロバイダから id_token をリクエストするためだけのものです。これはまだ Firebase へのログインではありません。したがって、この呼び出しが成功した後に Firebase にログインする必要があります。

Firebase ログインとユーザーのid_token

googleResponse変数を監視し、それが変更されたときにエフェクトを作成しましょう。

useEffect(() => {
  if (googleResponse?.type === 'success') {
    // ...Firebase login will come here
  }
}, [googleResponse]);

これで、Firebase ログインは私たちを通してパッチするための資格情報が必要です。この資格情報は、プロバイダの id_token から作成できます。これを行うために、firebase パッケージで提供されているGoogleAuthProviderを使用します。

import { GoogleAuthProvider } from 'firebase/auth/react-native';

そして、資格情報を生成するためにそれを使用します:

const credentials = GoogleAuthProvider.credential(
  googleResponse.params.id_token
);

これまでの useEffect は以下のようになっています:

useEffect(() => {
  if (googleResponse?.type === 'success') {
    const credentials = GoogleAuthProvider.credential(
      googleResponse.params.id_token
    );
    // ...Firebase login will come here
  }
}, [googleResponse]);

これで、firebase パッケージからも提供されている Firebase ログインを呼び出す必要があります。しかし、それを行う前に、以下のようにコンポーネントの外部で firebase auth インスタンスを設定する必要があります

// create Firebase auth instace
const auth = initializeAuth(app);

これで、ログイン関数を書き出すことができます。

const signInResponse = await signInWithCredential(auth, credentials);

コードの分離をより良くするために、これをuseCallbackに移動させる方が良いでしょう。

タイプ OAuthCredentialも firebase パッケージから来ています。

// Function that logs into firebase using the credentials from an OAuth provider
const loginToFirebase = useCallback(async (credentials: OAuthCredential) => {
  const signInResponse = await signInWithCredential(auth, credentials);
}, []);

リクエストが成功すると、signInResponseuserが取得されるはずです。

これで、現在のコンポーネントは以下のようになっています:

import { useEffect, useCallback } from 'react';
import { Button } from 'react-native';
import { useIdTokenAuthRequest as useGoogleIdTokenAuthRequest } from 'expo-auth-session/providers/google';
import {
  signInWithCredential,
  GoogleAuthProvider,
  initializeAuth,
  OAuthCredential,
} from 'firebase/auth/react-native';
import {
  androidClientId,
  app,
  expoClientId,
  iosClientId,
} from './firebaseConfig';

// create Firebase auth instace
const auth = initializeAuth(app);

export const Auth: React.FC = () => {
  // Hook that gives us the function to authenticate our Google OAuth provider
  const [, googleResponse, promptAsyncGoogle] = useGoogleIdTokenAuthRequest({
    selectAccount: true,
    expoClientId,
    iosClientId,
    androidClientId,
  });

  // Handles the login via the Google Provider
  const handleLoginGoogle = async () => {
    await promptAsyncGoogle();
  };

  // Function that logs into firebase using the credentials from an OAuth provider
  const loginToFirebase = useCallback(async (credentials: OAuthCredential) => {
    const signInResponse = await signInWithCredential(auth, credentials);
  }, []);

  useEffect(() => {
    if (googleResponse?.type === 'success') {
      const credentials = GoogleAuthProvider.credential(
        googleResponse.params.id_token
      );
      loginToFirebase(credentials);
    }
  }, [googleResponse]);

  return <Button title={'Login'} onPress={handleLoginGoogle} />;
};

ユーザーをステートに保存し、SecureStorage での永続性を設定する

認証できるのは良いですが、ユーザーをコンポーネントに保存し、アプリが閉じられた場合でも認証が続くようにしましょう。

ステート変数を追加しましょう

User タイプは firebase パッケージから来ています)

const [user, setUser] = useState<User | null>(null);

この状態にユーザーを保存するためには、初期化されたauthインスタンスのonAuthStateChangedプロパティにリスナーを追加する必要があります。

useEffect(() => {
  auth.onAuthStateChanged((user) => {
    setUser(user);
  });
}, []);

この方法で、以下のように簡単にユーザーデータを画面に追加できます。

return (
  <>
    <Button title={'Login'} onPress={handleLoginGoogle} />
    {user && (
      <>
        <Text>Logged in as:</Text>
        <Text>{user.displayName}</Text>
        <Text>{user.email}</Text>
      </>
    )}
  </>
);

ロジックを拡張して、基本的にユーザーをサインアウトするログアウト機能を追加しましょう。幸い、前のリスナーがログアウトの状態変更も処理してくれるので、一つの関数を呼び出すだけで済みます。

const handleLogoutGoogle = useCallback(() => {
  auth.signOut();
}, []);

三項演算子を使用してビジュアルも追加しましょう:

return (
  <>
    {user ? (
      <>
        <Button title={'Logout'} onPress={handleLogoutGoogle} />
        <Text>Logged in as:</Text>
        <Text>{user.displayName}</Text>
        <Text>{user.email}</Text>
      </>
    ) : (
      <Button title={'Login'} onPress={handleLoginGoogle} />
    )}
  </>
);

最後に実行しなければいけないことは、アプリが閉じられたときでもユーザーを永続させることです。これは SecureStorage で行います。

トップにインポートします

import * as SecureStore from 'expo-secure-store';

そして、それを設定する方法は、firebase auth インスタンスのための独自の persistor を作成することです。

// Persistor for React Native using Secure Storage
const secureStoragePersistor = getReactNativePersistence({
  getItem(key) {
    // ...Get an item by key from storage
  },
  setItem(key, value) {
    // ...Set value to an item by key into storage
  },
  removeItem(key) {
    // ...Remove an item by key from the storage
  },
});

SecureStorageには getItemAsyncsetItemAsyncdeleteItemAsync関数があり、これらはgetReactNativePersistenceを拡張したものと型が対応しています。

ただし、1 つの問題があります。SecureStorage はアルファベットまたは数字の値を含むキーしか保存できるないため、これらの関数からのキーをこのルールと互換性があるものに変換する必要があります。

以下は、SecureStorageと互換性のある文字だけを保持する小さな正規表現の置換例です。

const replaceNonAlphaNumericValues = (key: string) =>
  key.replaceAll(/[^a-zA-Z\d\s]/g, '');

最終的に、persistor は次のようになります:

// Persistor for React Native using Secure Storage
const secureStoragePersistor = getReactNativePersistence({
  async getItem(key) {
    return SecureStore.getItemAsync(replaceNonAlphaNumericValues(key));
  },
  setItem(key, value) {
    return SecureStore.setItemAsync(replaceNonAlphaNumericValues(key), value);
  },
  removeItem(key) {
    return SecureStore.deleteItemAsync(replaceNonAlphaNumericValues(key));
  },
});

さて、初期化中の第 2 引数である deps オブジェクトを埋めて、auth インスタンスにそれを追加しましょう。

const auth = initializeAuth(app, {
  persistence: secureStoragePersistor,
});
<hr />

TLDR.:

最終形式のコンポーネントはどのように見えるかを見てみましょう。

import { useState, useEffect, useCallback } from 'react';
import { Button, Text } from 'react-native';
import { useIdTokenAuthRequest as useGoogleIdTokenAuthRequest } from 'expo-auth-session/providers/google';
import {
  signInWithCredential,
  GoogleAuthProvider,
  User,
  initializeAuth,
  getReactNativePersistence,
  OAuthCredential,
} from 'firebase/auth/react-native';
import {
  androidClientId,
  app,
  expoClientId,
  iosClientId,
} from './firebaseConfig';
import * as SecureStore from 'expo-secure-store';

// Util to remove non alphanumeric values from a string
const replaceNonAlphaNumericValues = (key: string) =>
  key.replaceAll(/[^a-zA-Z\d\s]/g, '');

// Persistor for React Native using Secure Storage
const secureStoragePersistor = getReactNativePersistence({
  async getItem(key) {
    return SecureStore.getItemAsync(replaceNonAlphaNumericValues(key));
  },
  setItem(key, value) {
    return SecureStore.setItemAsync(replaceNonAlphaNumericValues(key), value);
  },
  removeItem(key) {
    return SecureStore.deleteItemAsync(replaceNonAlphaNumericValues(key));
  },
});

// create Firebase auth instace
const auth = initializeAuth(app, {
  persistence: secureStoragePersistor,
});

export const Auth: React.FC = () => {
  const [accessToken, setAccessToken] = useState('');
  const [user, setUser] = useState<User | null>(null);

  // Sets the user to the state to be accessible in the component
  // @NOTE: This can be moved to a context so this component can act as a provider
  useEffect(() => {
    auth.onAuthStateChanged((user) => {
      setUser(user);
    });
  }, []);

  // Hook that gives us the function to authenticate our Google OAuth provider
  const [, googleResponse, promptAsyncGoogle] = useGoogleIdTokenAuthRequest({
    selectAccount: true,
    expoClientId,
    iosClientId,
    androidClientId,
  });

  // Handles the login via the Google Provider
  const handleLoginGoogle = async () => {
    await promptAsyncGoogle();
  };

  // Function that logs into firebase using the credentials from an OAuth provider
  const loginToFirebase = useCallback(async (credentials: OAuthCredential) => {
    const signInResponse = await signInWithCredential(auth, credentials);
    const token = await signInResponse.user.getIdToken();
  }, []);

  useEffect(() => {
    if (googleResponse?.type === 'success') {
      const credentials = GoogleAuthProvider.credential(
        googleResponse.params.id_token
      );
      loginToFirebase(credentials);
    }
  }, [googleResponse]);

  // Handles the logout using Google Provider
  const handleLogoutGoogle = useCallback(() => {
    auth.signOut();
  }, []);

  return (
    <>
      {user ? (
        <>
          <Button title={'Logout'} onPress={handleLogoutGoogle} />
          <Text>Logged in as:</Text>
          <Text>{user.displayName}</Text>
          <Text>{user.email}</Text>
        </>
      ) : (
        <Button title={'Login'} onPress={handleLoginGoogle} />
      )}
    </>
  );
};

Scriptideは、カスタムで複雑なB2Bソフトウェアソリューションに特化した高度なソフトウェア開発会社です。デジタルトランスフォーメーション、ウェブおよびモバイル開発、AI、ブロックチェーンなど、さまざまなソリューションを提供しています。

無料のIT相談を受けてください。お話しできることを楽しみにしています。

こちらの記事もおすすめです!

2024年のReact NativeでのExpo

詳細はこちら

2024年にReact NativeでExpoを使用すべきか?

次のReact NativeプロジェクトにExpoを使用するべきかどうかについての終わりのない議論が終わりに近づいています。ネタバレ注意:ExpoもReact Nativeも勝者です。

#dev

APR 10, 2024

16 min read

VS CodeエディタでのObjective-Cコード。

詳細はこちら

Objective-Cを使用してElectronアプリでMacOSのアクティブウィンドウを追跡する

この記事では、Objective-C++を使用してMacOSでElectronアプリのアクティブウィンドウを簡単に追跡する方法を紹介します。Electronなしでネイティブにも使用できます。

#dev

OCT 05, 2022

7 min read

'承諾'をクリックすることで、こちらに記載されているすべてのクッキーの使用に同意します: プライバシーポリシー.

© 2024 Scriptide Ltd.

全著作権所有

D-U-N-S® 番号:40-142-5341

VAT ID(ハンガリー): HU27931114

会社登録番号(ハンガリー): 01 09 357677

プライバシーポリシー