{"id":"firebase-auth","name":"firebase-auth","summary":"認証の設定、認証状態の管理、メール/パスワードやソーシャルサインインの実装、認証エラーの処理、ユーザー管理の際に使います。","body":"# Firebase Authentication Skill\n\nThis skill defines how to correctly use Firebase Authentication in Flutter applications.\n\n## When to Use\n\nUse this skill when:\n\n* Setting up Firebase Authentication in a Flutter project.\n* Listening to authentication state changes.\n* Implementing email/password, phone number, or social sign-in.\n* Managing user profiles, account linking, or MFA.\n* Handling authentication errors (including iOS `recaptcha-sdk-not-linked` for phone auth).\n* Applying security best practices for auth flows.\n\n---\n\n## 1. Setup and Configuration\n\n```\nflutter pub add firebase_auth\n```\n\n```dart\nimport 'package:firebase_auth/firebase_auth.dart';\n```\n\n- Enable desired authentication providers in the **Firebase console** before using them.\n- Initialize Firebase before using any Firebase Authentication features.\n\n**Local emulator for testing:**\n\n```dart\nFuture<void> main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  await Firebase.initializeApp();\n  await FirebaseAuth.instance.useAuthEmulator('localhost', 9099);\n  // ...\n}\n```\n\n---\n\n## 2. Authentication State Management\n\nUse the appropriate stream based on what you need to observe:\n\n| Stream | Fires when |\n|---|---|\n| `authStateChanges()` | User signs in or out |\n| `idTokenChanges()` | ID token changes (including custom claims) |\n| `userChanges()` | User data changes (e.g., profile updates) |\n\n```dart\nFirebaseAuth.instance\n  .authStateChanges()\n  .listen((User? user) {\n    if (user == null) {\n      print('User is currently signed out!');\n    } else {\n      print('User is signed in!');\n    }\n  });\n```\n\n- Listen to these streams **immediately** when the app starts to handle the initial auth state.\n- Custom claims are only available after sign-in, re-authentication, token expiration, or manual token refresh.\n\n---\n\n## 3. Email and Password Authentication\n\n**Create a new account:**\n\n```dart\ntry {\n  final credential = await FirebaseAuth.instance.createUserWithEmailAndPassword(\n    email: emailAddress,\n    password: password,\n  );\n} on FirebaseAuthException catch (e) {\n  if (e.code == 'weak-password') {\n    print('The password provided is too weak.');\n  } else if (e.code == 'email-already-in-use') {\n    print('The account already exists for that email.');\n  }\n} catch (e) {\n  print(e);\n}\n```\n\n**Sign in:**\n\n```dart\ntry {\n  final credential = await FirebaseAuth.instance.signInWithEmailAndPassword(\n    email: emailAddress,\n    password: password,\n  );\n} on FirebaseAuthException catch (e) {\n  if (e.code == 'invalid-credential') {\n    // Email enumeration protection enabled (default since Sep 2023):\n    // replaces 'user-not-found' and 'wrong-password'.\n    print('Invalid email or password.');\n  } else if (e.code == 'user-not-found') {\n    print('No user found for that email.');\n  } else if (e.code == 'wrong-password') {\n    print('Wrong password provided for that user.');\n  }\n}\n```\n\n- Verify the user's email address after account creation.\n- Firebase rate-limits new email/password sign-ups from the same IP to protect against abuse.\n- On iOS/macOS, authentication state persists between app re-installs via the system keychain.\n- Since September 2023, Firebase enables **email enumeration protection** by default on new projects, replacing `user-not-found` and `wrong-password` with `invalid-credential`. Manage this in the Firebase console under **Authentication > Settings**.\n- When email enumeration protection is enabled, `sendPasswordResetEmail()` may complete without an error even if the email is not registered. Treat this as expected behavior and do not use password-reset responses to infer whether an email exists.\n\n---\n\n## 4. Social Authentication\n\n**Google Sign-In (native platforms):**\n\n```dart\nFuture<UserCredential> signInWithGoogle() async {\n  final GoogleSignInAccount? googleUser = await GoogleSignIn.instance.authenticate();\n  final GoogleSignInAuthentication googleAuth = googleUser.authentication;\n  final credential = GoogleAuthProvider.credential(idToken: googleAuth.idToken);\n  return await FirebaseAuth.instance.signInWithCredential(credential);\n}\n```\n\n**Google Sign-In (web):**\n\n```dart\nFuture<UserCredential> signInWithGoogle() async {\n  GoogleAuthProvider googleProvider = GoogleAuthProvider();\n  googleProvider.addScope('https://www.googleapis.com/auth/contacts.readonly');\n  googleProvider.setCustomParameters({'login_hint': 'user@example.com'});\n  return await FirebaseAuth.instance.signInWithPopup(googleProvider);\n}\n```\n\n- Configure platform-specific settings for each provider (e.g., SHA1 key for Google Sign-In on Android).\n- If a user signs in with a social provider after registering with the same email manually, Firebase's trusted provider concept will automatically change their authentication provider.\n- On Android, `signInWithProvider` opens a Chrome Custom Tab. If `AndroidManifest.xml` contains `android:taskAffinity=\"\"` (Flutter's default), the tab closes when the user switches apps (e.g., to use a password manager), causing a `web-context-already-presented` error. Remove `android:taskAffinity=\"\"` to fix this.\n- When signing in with Apple, add the `email` and `name` scopes to present the full first-time sign-in UI (including \"Share/Hide email\"):\n  ```dart\n  final appleProvider = AppleAuthProvider();\n  appleProvider.addScope('email');\n  appleProvider.addScope('name');\n  ```\n- To revoke Apple auth tokens after sign-in, use the appropriate API per platform:\n  - **Apple platforms** (iOS/macOS/web): use `revokeTokenWithAuthorizationCode()` with the authorization code from `userCredential.additionalUserInfo?.authorizationCode`.\n  - **Android**: use `revokeAccessToken()` with the access token from `userCredential.credential?.accessToken`.\n  ```dart\n  // Apple platforms (iOS/macOS/web)\n  final authCode = userCredential.additionalUserInfo?.authorizationCode;\n  if (authCode != null) {\n    await FirebaseAuth.instance.revokeTokenWithAuthorizationCode(authCode);\n  }\n\n  // Android\n  final accessToken = userCredential.credential?.accessToken;\n  if (accessToken != null) {\n    await FirebaseAuth.instance.revokeAccessToken(accessToken);\n  }\n  ```\n\n---\n\n## 5. Phone Number Authentication\n\nBefore using phone authentication, ensure platform-specific prerequisites are met:\n\n- **Android**: SHA-1 hashes must be configured in the Firebase console and Google Play Integrity API enabled.\n- **iOS**: APNs authentication key must be configured with FCM and background modes for remote notifications enabled.\n- **Web**: Add your application's domain to the Firebase console under **OAuth redirect domains**.\n\nPhone number sign-in is only supported on real devices and the web. Testing on device emulators is not supported.\n\n**iOS: `recaptcha-sdk-not-linked` error**\n\nOn iOS, `verifyPhoneNumber` can throw `FirebaseAuthException` with code `recaptcha-sdk-not-linked` when Identity Platform expects reCAPTCHA Enterprise but the native SDK is not linked. This cannot be resolved from Dart — fix it at the native iOS or GCP level:\n\n- **Recommended**: Link the reCAPTCHA Enterprise iOS SDK in Xcode following [Google's guide](https://cloud.google.com/recaptcha-enterprise/docs/instrument-ios-apps).\n- **Alternative**: Disable reCAPTCHA SMS defense via the Identity Toolkit [`projects.updateConfig`](https://cloud.google.com/identity-platform/docs/reference/rest/v2/projects/updateConfig) REST API (set `recaptchaConfig.phoneEnforcementState` to `OFF` and `recaptchaConfig.useSmsTollFraudProtection` to `false`). See the [official steps](https://cloud.google.com/identity-platform/docs/recaptcha-tfp#disable_recaptcha_sms_defense). This reduces fraud protection — prefer linking the SDK.\n- If the SDK uses a Safari view controller-hosted challenge, handle the return URL using `uni_links`/`app_links` or `application:openURL:` in the iOS runner.\n\n---\n\n## 6. Error Handling\n\n- Always use `try-catch` with `FirebaseAuthException`.\n- Check `e.code` to identify specific error types.\n- Handle `account-exists-with-different-credential` by fetching sign-in methods for the email and guiding users through the correct flow.\n- Handle `too-many-requests` with retry logic or user feedback.\n- Handle `operation-not-allowed` by ensuring the provider is enabled in the Firebase console.\n- On iOS, `recaptcha-sdk-not-linked` during `verifyPhoneNumber` is raised by the native Firebase iOS Auth SDK and requires native setup or GCP configuration changes — it cannot be fixed from Dart code alone.\n\n---\n\n## 7. User Management\n\n```dart\n// Update profile\nawait FirebaseAuth.instance.currentUser?.updateProfile(\n  displayName: \"Jane Q. User\",\n  photoURL: \"https://example.com/jane-q-user/profile.jpg\",\n);\n\n// Update email (sends verification to new address first)\nawait user?.verifyBeforeUpdateEmail(\"newemail@example.com\");\n```\n\n- Use `verifyBeforeUpdateEmail()` — **not** `updateEmail()` — to change a user's email. The email only updates after the user verifies it.\n- Store only essential info in the auth profile; use a database for additional user data.\n- Use `linkWithCredential()` to connect multiple auth providers to a single account.\n- Verify the user's identity before linking new credentials.\n- Use `fetchSignInMethodsForEmail()` when handling account linking.\n\n---\n\n## 8. Security Best Practices\n\n- Never store sensitive authentication credentials in client-side code.\n- Monitor auth state changes for proper session management.\n- Validate user input before submitting authentication requests to prevent injection attacks.\n- Call `FirebaseAuth.instance.signOut()` when users exit the app.\n- For sensitive operations, re-authenticate users with `reauthenticateWithCredential()`.\n- Enforce strong password policies for email/password auth.\n- In Realtime Database and Cloud Storage Security Rules, use the `auth` variable to get the signed-in user's UID for access control.\n- Use multi-factor authentication for sensitive applications.\n\n---\n\n## 9. Multi-Factor Authentication\n\n> **Security warning:** Avoid SMS-based MFA. SMS is insecure and easy to compromise or spoof.\n\n> **Platform limitation:** Windows does not support MFA. MFA with multiple tenants is not supported on Flutter.\n\n- Enable at least one MFA-compatible provider before implementing MFA.\n\n---\n\n## 10. Email Link Authentication\n\n> **Important:** Firebase Dynamic Links is deprecated for email link authentication. Firebase Hosting is now used to send sign-in links.\n\n- Set `handleCodeInApp: true` in `ActionCodeSettings` — sign-in must always be completed in the app.\n- Store the user's email locally (e.g., `SharedPreferences`) when sending the sign-in link.\n- **Never** pass the user's email in redirect URL parameters — this enables session injection attacks.\n- Use HTTPS URLs in production to prevent link interception.\n- Configure the app to detect incoming links and parse the underlying deep link for sign-in completion.\n\n---\n\n## References\n\n- [Firebase Authentication Flutter documentation](https://firebase.google.com/docs/auth/flutter/start)\n- [Email/password authentication](https://firebase.google.com/docs/auth/flutter/password-auth)\n- [Federated identity & social sign-in](https://firebase.google.com/docs/auth/flutter/federated-auth)\n- [Multi-factor authentication](https://firebase.google.com/docs/auth/flutter/multi-factor)","author":"@evanca","ownerProfile":null,"authorContacts":null,"sourceUrl":"https://github.com/evanca/flutter-ai-rules/tree/main/skills/firebase-auth","license":"MIT","category":"security","lang":"en","tokens":2442,"stars":0,"calls30d":1,"claimed":false,"visibility":"public","origin":"crawler","version":"0.1.0","createdAt":"2026-08-22","updatedAt":"2026-08-22","files":[],"requires":{"mcp":[],"tools":[]},"safety":{"flags":[],"scannedAt":"2026-08-22","hasScripts":false,"networkEndpoints":["cloud.google.com","firebase.google.com","www.googleapis.com"]}}