"react-native-google-signin" を使わないGoogle認証 [React Native/Expo]
先日自作アプリにLINEログインを追加したところ、Apple Sign-inがないことを理由にAppStoreでリジェクトされてしまいました。
本記事は、ExpoでApple Sign-inをはじめとするネイティブOSのサインイン機能を実装するため調べた知見をまとめたものです。
Expoを利用したReact Nativeアプリでネイティブ認証を実装する際、expo-apple-authentication
や@react-native-google-signin/google-signin
といったライブラリが広く使われています。しかし、これらのライブラリには
- 複数のライブラリ管理: AppleとGoogleで異なるライブラリを導入する必要があり、メンテナンス性が低下します。
-
古いAPIへの依存:
@react-native-google-signin/google-signin
の無償版は、Androidで非推奨(deprecated)となったcom.google.android.gms:play-services-auth
に依存しています。これにより、Googleが推奨するモダンな認証UI(One Tapなど)が利用できず、ネイティブ実装に比べユーザーエクスペリエンスが低下します。
のような課題があります。
本記事で紹介するreact-native-credentials-manager
は、単一のパッケージで両プラットフォームの認証APIを呼び出し、この課題を解決します。
(GitHubリポジトリ)
- 統一のAPI: プラットフォームごとの違いを吸収し、一貫したAPIでAppleとGoogleのサインインを実装できます。
- 優れたUX: Androidでは新しいCredential Manager APIを利用するため、非推奨ライブラリへの依存なく、優れたUI/UXを提供します。iOSでも同様のOS標準ライブラリをラップしています。
- 高い拡張性: Google/Appleサインインに加え、パスキーを利用したサインインにも対応しており、モダンなサインインオプションを提供できます。
この記事では、react-native-credentials-manager
とSupabaseを連携させ、シームレスでモダンなログインを実装する方法を解説します。
必要なパッケージのインストール
まず、プロジェクトに必要な依存関係をインストールします。
npm install react-native-credentials-manager uuid @supabase/supabase-js
npm install --save-dev @types/uuid
(Yarnの場合)
yarn add react-native-credentials-manager uuid @supabase/supabase-js
yarn add --dev @types/uuid
バックエンド(Supabase)の設定
今回は、認証処理のバックエンドとしてSupabaseを利用する場合について解説します。
1. Supabaseプロジェクトの準備(省略)
Supabaseプロジェクトを作成し、プロジェクトに組み込みます。
2. 認証プロバイダの有効化
Supabaseのダッシュボードで、[Authentication] > [Providers] に移動し、AppleとGoogleを有効にします。各プロバイダに必要なOAuthクライアントIDやシークレット情報を設定してください。
Googleプロバイダには、後述する「ウェブアプリケーション」タイプのクライアントIDとクライアントシークレットを設定します。
プラットフォーム固有の設定
次に、iOSとAndroidそれぞれのプラットフォームでネイティブ認証を有効にするための設定を行います。
ドメイン認証
ライブラリのドキュメント にある通り、AppleとGoogleに対しドメイン認証を行います。
iOS (Appleサインイン)
- Apple DeveloperのIdentifiersページから自分のアプリのidを選択し、"Sign In with Apple" を有効化します。
-
app.config.ts
(またはexpo.json
) に、Appleサインインを有効化する設定を追加します。バンドルIDや関連ドメインはご自身の環境に合わせてください。app.config.ts// app.config.ts または expo.json export default { ios: { usesAppleSignIn: true, bundleIdentifier: "your.app.bundle.id", infoPlist: { // 必要に応じて追加のplist設定を追加 }, associatedDomains: [ "applinks:yourdomain.com", "webcredentials:yourdomain.com", ] } };
Android (Googleサインイン)
Google Cloud Consoleでの認証情報設定は、Supabaseとネイティブ認証を連携させる上で間違いやすいポイントです。(私もここで詰まりました)
まず、「Androidアプリ」と「ウェブアプリケーション」の2種類のOAuthクライアントIDを作成する必要があります。
-
Androidアプリ:
- Google Cloud Consoleの「APIとサービス」 > 「認証情報」で「認証情報を作成」 > 「OAuthクライアントID」を選択します。
- アプリケーションの種類で「Android」を選択します。
- アプリのパッケージ名とSHA-1証明書フィンガープリントを必ず登録してください。これにより、GoogleはあなたのAndroidアプリからのリクエストを正しく識別できます。
-
ウェブアプリケーション:
- 同様に「認証情報を作成」 > 「OAuthクライアントID」を選択します。
- アプリケーションの種類で「ウェブアプリケーション」を選択します。
- このクライアントIDとクライアントシークレットを、SupabaseのGoogleプロバイダ設定と、後述するアプリの実装コードで使用します。
※注意: Supabaseとアプリコード (serverClientId
) の両方で使用するのは、ウェブアプリケーションのクライアントIDです。AndroidアプリのクライアントIDは、コードに直接記述することはありません。
export default {
android: {
package: "your.app.package.name",
permissions: [
"android.permission.ACCESS_NETWORK_STATE",
// その他の権限...
]
}
};
実装
ネイティブ認証ロジックの作成
プラットフォームを判別し、それぞれに対応するサインアップ処理を呼び出す関数をcredentialManager.ts
として実装します。YOUR_GOOGLE_CLIENT_ID
には、前述の通りGoogle Cloud Consoleで発行した「ウェブアプリケーション」タイプのクライアントIDを設定してください。
import { Platform } from "react-native";
import { signUpWithApple, signUpWithGoogle } from "react-native-credentials-manager";
import { v4 as uuidv4 } from 'uuid';
// 一貫したデータ構造のためのユーザーインターフェースを定義
interface PlatformCredentialUser {
name?: string;
givenName?: string;
familyName?: string;
photo?: string;
email?: string;
}
// レスポンスインターフェースを定義
interface PlatformCredentialResponse {
type: "google" | "apple";
token: string;
id: string;
user: PlatformCredentialUser;
}
async function platformSpecificSignUp(): Promise<PlatformCredentialResponse> {
try {
if (Platform.OS === "android") {
// Android: Googleサインイン
const googleCredential = await signUpWithGoogle({
serverClientId: "YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com", // ⚠️ ウェブアプリケーションのクライアントID
autoSelectEnabled: true,
});
return {
type: "google",
token: googleCredential.idToken,
id: googleCredential.id,
user: {
name: googleCredential.displayName,
givenName: googleCredential.givenName,
familyName: googleCredential.familyName,
photo: googleCredential.profilePicture,
},
};
} else {
// iOS: Appleサインイン
const appleCredential = await signUpWithApple({
nonce: uuidv4(),
requestedScopes: ["fullName", "email"],
});
return {
type: "apple",
token: appleCredential.idToken,
id: appleCredential.id,
user: {
name: appleCredential.displayName,
givenName: appleCredential.givenName,
familyName: appleCredential.familyName,
email: appleCredential.email,
},
};
}
} catch (error) {
console.error("サインイン処理に失敗: ", error);
throw error;
}
}
export { platformSpecificSignUp, type PlatformCredentialResponse };
Reactコンポーネントへの組み込み
作成した認証ロジックをReactコンポーネントから呼び出し、SupabaseのsignInWithIdToken
メソッドに連携させます。
import React, { useState } from 'react';
import { View, Button, Alert, Text, Platform } from 'react-native';
import { platformSpecificSignUp } from './credentialManager';
import { supabase } from './supabase'; // Supabaseクライアント
const LoginScreen: React.FC = () => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
const handleNativeLogin = async () => {
try {
setLoading(true);
// クレデンシャルを取得
const result = await platformSpecificSignUp();
// ネイティブトークンを使用してSupabaseでサインイン
const {error, data: {user}} = await supabase.auth.signInWithIdToken({
provider: result.type, // "apple" か "google"
token: result.token
});
if (error) {
throw error;
}
setUser(user);
console.log('ログイン成功:', user);
} catch (error) {
console.error('ログインに失敗しました:', error);
Alert.alert('ログイン失敗', error.message || 'しばらくしてから再試行してください。');
} finally {
setLoading(false);
}
};
const handleSignOut = async () => {
await supabase.auth.signOut();
setUser(null);
};
if (user) {
return (
<View style={{ padding: 20 }}>
<Text>ようこそ、{user.user_metadata?.name || user.email || 'ユーザー'}さん!</Text>
<Text>ユーザーID: {user.id}</Text>
<Button
title="サインアウト"
onPress={handleSignOut}
/>
</View>
);
}
return (
<View style={{ padding: 20 }}>
{Platform.select({
ios: (
<Button
title={loading ? "サインイン中..." : "Appleで続ける"}
onPress={handleNativeLogin}
disabled={loading}
/>
),
android: (
<Button
title={loading ? "サインイン中..." : "Googleで続ける"}
onPress={handleNativeLogin}
disabled={loading}
/>
)
})}
</View>
);
};
export default LoginScreen;
結論
react-native-credentials-manager
を利用することで、手軽にネイティブアプリ同様のログインUXを実現できます。
本記事で紹介したライブラリ react-native-credentials-manager
を開発・公開してくださったokwasniewski氏に感謝します。
本記事が、あなたのアプリ開発の一助となれば幸いです。
Discussion