mcpField
F
firebase-auth

firebase-auth

github.com/evancaセキュリティ665191 回インストール今日更新
リポジトリ

firebase-auth について

firebase-auth は、Flutter アプリで Firebase Authentication を実装するためのガイドスキルです。サインイン、ユーザーとセッションの管理、トラブルシューティング、安全対策を扱います。

主な機能

  1. 01Firebase Authentication の設定
  2. 02認証状態ストリームの監視
  3. 03メール、電話、ソーシャルログインの実装
  4. 04プロフィール、アカウント連携、MFA の管理
  5. 05エラー対応とセキュリティ対策

活用シーン

  1. 01Flutter アプリへの認証追加
  2. 02サインイン・サインアウトの状態変化への対応
  3. 03プロバイダー連携やプロフィール更新
  4. 04認証エラーの調査とフローの安全強化

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/evanca/flutter-ai-rules --skill firebase-auth

インストール後、Claude Code で/firebase-authを入力して読み込まれたか確認します。

詳細

Firebase Authentication スキル

このスキルでは、Flutter アプリケーションで Firebase Authentication を正しく使用する方法を説明します。

使用するタイミング

次の場合にこのスキルを使用します。

  • Flutter プロジェクトで Firebase Authentication をセットアップするとき。
  • 認証状態の変更を監視するとき。
  • メールアドレス/パスワード、電話番号、ソーシャルログインを実装するとき。
  • ユーザープロフィール、アカウントのリンク、MFA を管理するとき。
  • 認証エラー(電話番号認証での iOS の recaptcha-sdk-not-linked を含む)に対処するとき。
  • 認証フローにセキュリティのベストプラクティスを適用するとき。

1. セットアップと設定

flutter pub add firebase_auth
import 'package:firebase_auth/firebase_auth.dart';
  • 使用する前に、Firebase コンソールで必要な認証プロバイダーを有効にします。
  • Firebase Authentication の機能を使用する前に、Firebase を初期化します。

テスト用のローカルエミュレーター:

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  await FirebaseAuth.instance.useAuthEmulator('localhost', 9099);
  // ...
}

2. 認証状態の管理

監視したい内容に応じて、適切なストリームを使用します。

ストリーム発火するタイミング
authStateChanges()ユーザーのサインインまたはサインアウト時
idTokenChanges()ID トークンの変更時(カスタムクレームを含む)
userChanges()ユーザーデータの変更時(プロフィールの更新など)
FirebaseAuth.instance
  .authStateChanges()
  .listen((User? user) {
    if (user == null) {
      print('User is currently signed out!');
    } else {
      print('User is signed in!');
    }
  });
  • アプリの初回認証状態を処理するため、起動直後にこれらのストリームを監視します。
  • カスタムクレームを取得できるのは、サインイン、再認証、トークンの有効期限切れ、または手動でのトークン更新の後に限られます。

3. メールアドレスとパスワードによる認証

新しいアカウントを作成する:

try {
  final credential = await FirebaseAuth.instance.createUserWithEmailAndPassword(
    email: emailAddress,
    password: password,
  );
} on FirebaseAuthException catch (e) {
  if (e.code == 'weak-password') {
    print('The password provided is too weak.');
  } else if (e.code == 'email-already-in-use') {
    print('The account already exists for that email.');
  }
} catch (e) {
  print(e);
}

サインインする:

try {
  final credential = await FirebaseAuth.instance.signInWithEmailAndPassword(
    email: emailAddress,
    password: password,
  );
} on FirebaseAuthException catch (e) {
  if (e.code == 'invalid-credential') {
    // Email enumeration protection enabled (default since Sep 2023):
    // replaces 'user-not-found' and 'wrong-password'.
    print('Invalid email or password.');
  } else if (e.code == 'user-not-found') {
    print('No user found for that email.');
  } else if (e.code == 'wrong-password') {
    print('Wrong password provided for that user.');
  }
}
  • アカウント作成後に、ユーザーのメールアドレスを確認します。
  • Firebase は不正利用を防ぐため、同じ IP アドレスからの新しいメールアドレス/パスワードによる登録をレート制限します。
  • iOS/macOS では、システムのキーチェーンを介して、アプリを再インストールしても認証状態が保持されます。
  • 2023 年 9 月以降、新しいプロジェクトでは Firebase によりメールアドレス列挙保護がデフォルトで有効になっており、user-not-found と wrong-password は invalid-credential に置き換えられます。この設定は Firebase コンソールの Authentication > Settings で管理できます。
  • メールアドレス列挙保護が有効な場合、登録されていないメールアドレスでも sendPasswordResetEmail() がエラーなしで完了することがあります。これは想定どおりの動作です。メールアドレスの存在を推測するために、パスワードリセットの応答を使用しないでください。

Apple アプリ間で認証状態を共有する:

Apple プラットフォームでは、同じデベロッパーアカウントに属するアプリ間で認証状態を共有するには、共有 Keychain アクセスグループに保存します。各アプリで同じアクセスグループを指定して Keychain Sharing 機能を有効にし、Firebase Auth に完全修飾アクセスグループを設定します。

await FirebaseAuth.instance.setSettings(
  userAccessGroup: 'TEAMID.com.example.group1',
);

デフォルトの Keychain から共有アクセスグループに切り替えると、移行しない限り既存のユーザーはサインアウトされます。現在のセッション(匿名セッションを含む)を保持するには、migrateCurrentUser: true(userAccessGroup が null 以外である必要があります)を渡します。Auth の復元前に既存のセッションを Keychain から直接読み取るため、アプリ起動時に他の Auth 呼び出しより先に安全に実行できます。移行によって、移行先のアクセスグループにすでに保存されているユーザーが上書きされる場合があります。


4. ソーシャル認証

Google サインイン(ネイティブプラットフォーム):

Future<UserCredential> signInWithGoogle() async {
  final GoogleSignInAccount? googleUser = await GoogleSignIn.instance.authenticate();
  final GoogleSignInAuthentication googleAuth = googleUser.authentication;
  final credential = GoogleAuthProvider.credential(idToken: googleAuth.idToken);
  return await FirebaseAuth.instance.signInWithCredential(credential);
}

Google サインイン(ウェブ):

Future<UserCredential> signInWithGoogle() async {
  GoogleAuthProvider googleProvider = GoogleAuthProvider();
  googleProvider.addScope('https://www.googleapis.com/auth/contacts.readonly');
  googleProvider.setCustomParameters({'login_hint': '[email protected]'});
  return await FirebaseAuth.instance.signInWithPopup(googleProvider);
}
  • 各プロバイダーについて、プラットフォーム固有の設定を行います(例:Android で Google サインインを使用する場合の SHA1 キー)。
  • ユーザーが同じメールアドレスで手動登録した後にソーシャルプロバイダーでサインインすると、Firebase の信頼済みプロバイダーの仕組みにより、認証プロバイダーが自動的に変更されます。
  • Android では、signInWithProvider によって Chrome Custom Tab が開きます。AndroidManifest.xml に android:taskAffinity=""(Flutter のデフォルト)が含まれている場合、ユーザーがアプリを切り替える(パスワードマネージャーを使用するなど)とタブが閉じ、web-context-already-presented エラーが発生します。これを解決するには、android:taskAffinity="" を削除します。
  • Apple でサインインする際は、初回サインイン時の UI(「メールアドレスを共有/非公開」など)をすべて表示するため、email と name のスコープを追加します。
    final appleProvider = AppleAuthProvider();
    appleProvider.addScope('email');
    appleProvider.addScope('name');
    
  • サインイン後に Apple 認証トークンを取り消すには、プラットフォームに応じた API を使用します。
    • Apple プラットフォーム(iOS/macOS/ウェブ):userCredential.additionalUserInfo?.authorizationCode の認証コードを使用して revokeTokenWithAuthorizationCode() を呼び出します。
    • Android:userCredential.credential?.accessToken のアクセストークンを使用して revokeAccessToken() を呼び出します。
    // Apple platforms (iOS/macOS/web)
    final authCode = userCredential.additionalUserInfo?.authorizationCode;
    if (authCode != null) {
      await FirebaseAuth.instance.revokeTokenWithAuthorizationCode(authCode);
    }
    
    // Android
    final accessToken = userCredential.credential?.accessToken;
    if (accessToken != null) {
      await FirebaseAuth.instance.revokeAccessToken(accessToken);
    }
    

5. 電話番号認証

電話番号認証を使用する前に、プラットフォーム固有の前提条件が満たされていることを確認してください。

  • Android:Firebase コンソールで SHA-1 ハッシュを設定し、Google Play Integrity API を有効にする必要があります。
  • iOS:FCM で APNs 認証キーを設定し、リモート通知のバックグラウンドモードを有効にする必要があります。
  • ウェブ:Firebase コンソールの OAuth リダイレクトドメイン に、アプリケーションのドメインを追加します。

電話番号でのサインインは、実機とウェブでのみサポートされています。デバイスエミュレーターでのテストはサポートされていません。

iOS:recaptcha-sdk-not-linked エラー

iOS では、Identity Platform が reCAPTCHA Enterprise を必要としている一方、ネイティブ SDK がリンクされていない場合、verifyPhoneNumber がコード recaptcha-sdk-not-linked の FirebaseAuthException をスローすることがあります。これは Dart からは解決できません。ネイティブ iOS または GCP 側で修正してください。

  • 推奨:Google のガイドに従って、Xcode で reCAPTCHA Enterprise iOS SDK をリンクします。
  • 代替方法:Identity Toolkit の projects.updateConfig REST API を使用して reCAPTCHA SMS 防御を無効にします(recaptchaConfig.phoneEnforcementState を OFF に、recaptchaConfig.useSmsTollFraudProtection を false に設定します)。公式手順を参照してください。不正利用への保護が弱まるため、SDK のリンクを優先してください。
  • SDK が Safari ビューコントローラーでホストされたチャレンジを使用する場合は、iOS ランナーで uni_links/app_links または application:openURL: を使用して戻り先 URL を処理します。

6. エラー処理

  • 常に FirebaseAuthException を使って try-catch を行います。
  • e.code を確認して、具体的なエラーの種類を特定します。
  • account-exists-with-different-credential は、メールアドレスに対応するサインイン方法を取得し、適切なフローに沿ってユーザーを案内して処理します。
  • too-many-requests は、再試行ロジックまたはユーザーへのフィードバックで処理します。
  • operation-not-allowed は、Firebase コンソールでプロバイダーが有効になっていることを確認して処理します。
  • iOS で verifyPhoneNumber 中に発生する recaptcha-sdk-not-linked は、ネイティブ Firebase iOS Auth SDK によって通知されるもので、ネイティブ側のセットアップまたは GCP の設定変更が必要です。Dart コードだけでは修正できません。

7. ユーザー管理

// Update profile
await FirebaseAuth.instance.currentUser?.updateProfile(
  displayName: "Jane Q. User",
  photoURL: "https://example.com/jane-q-user/profile.jpg",
);

// Update email (sends verification to new address first)
await user?.verifyBeforeUpdateEmail("[email protected]");
  • ユーザーのメールアドレスを変更するには、updateEmail() ではなく verifyBeforeUpdateEmail() を使用します。ユーザーがメールアドレスを確認した後にのみ更新されます。
  • 認証プロフィールには必要最小限の情報のみを保存し、追加のユーザーデータにはデータベースを使用します。
  • linkWithCredential() を使用して、複数の認証プロバイダーを単一のアカウントに紐付けます。
  • 新しい認証情報を紐付ける前に、ユーザーの本人確認を行います。
  • アカウントの紐付けを処理する際は、fetchSignInMethodsForEmail() を使用します。

8. セキュリティのベストプラクティス

  • 機密性の高い認証情報をクライアント側のコードに保存しないでください。
  • 適切なセッション管理のため、認証状態の変更を監視します。
  • インジェクション攻撃を防ぐため、認証リクエストを送信する前にユーザー入力を検証します。
  • ユーザーがアプリを終了する際は、FirebaseAuth.instance.signOut() を呼び出します。
  • 機密性の高い操作では、reauthenticateWithCredential() を使用してユーザーを再認証します。
  • メールアドレスとパスワードによる認証には、強力なパスワードポリシーを適用します。
  • Realtime Database と Cloud Storage の Security Rules では、アクセス制御に auth 変数を使用して、サインイン済みユーザーの UID を取得します。
  • 機密性の高いアプリケーションでは、多要素認証を使用します。

9. 多要素認証

セキュリティ警告: SMS に基づく MFA は避けてください。SMS は安全性が低く、簡単に侵害または偽装される可能性があります。

プラットフォームの制限: Windows は MFA をサポートしていません。Flutter では、複数のテナントを使用した MFA はサポートされていません。

  • MFA を実装する前に、MFA に対応したプロバイダーを少なくとも 1 つ有効にします。

10. メールリンク認証

重要: メールリンク認証における Firebase Dynamic Links は非推奨になりました。現在は Firebase Hosting を使用してサインインリンクを送信します。

  • ActionCodeSettings で handleCodeInApp: true を設定します。サインインは必ずアプリ内で完了する必要があります。
  • サインインリンクを送信する際は、ユーザーのメールアドレスをローカル(例:SharedPreferences)に保存します。
  • リダイレクト URL のパラメーターにユーザーのメールアドレスを決して含めないでください。これにより、セッションインジェクション攻撃が可能になります。
  • リンクの傍受を防ぐため、本番環境では HTTPS URL を使用します。
  • アプリが受信リンクを検出し、サインインを完了するために基となるディープリンクを解析するよう設定します。

参考資料

コメント