firebase-auth について
firebase-auth は、Flutter アプリで Firebase Authentication を実装するためのガイドスキルです。サインイン、ユーザーとセッションの管理、トラブルシューティング、安全対策を扱います。
主な機能
- 01Firebase Authentication の設定
- 02認証状態ストリームの監視
- 03メール、電話、ソーシャルログインの実装
- 04プロフィール、アカウント連携、MFA の管理
- 05エラー対応とセキュリティ対策
活用シーン
- 01Flutter アプリへの認証追加
- 02サインイン・サインアウトの状態変化への対応
- 03プロバイダー連携やプロフィール更新
- 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); } - Apple プラットフォーム(iOS/macOS/ウェブ):
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.updateConfigREST 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 を使用します。
- アプリが受信リンクを検出し、サインインを完了するために基となるディープリンクを解析するよう設定します。