This skill defines how to correctly use Firebase Authentication in Flutter applications.
Use this skill when:
recaptcha-sdk-not-linked for phone auth).flutter pub add firebase_auth
import 'package:firebase_auth/firebase_auth.dart';
Local emulator for testing:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
await FirebaseAuth.instance.useAuthEmulator('localhost', 9099);
// ...
}
Use the appropriate stream based on what you need to observe:
| Stream | Fires when |
|---|---|
authStateChanges() | User signs in or out |
idTokenChanges() | ID token changes (including custom claims) |
userChanges() | User data changes (e.g., profile updates) |
FirebaseAuth.instance
.authStateChanges()
.listen((User? user) {
if (user == null) {
print('User is currently signed out!');
} else {
print('User is signed in!');
}
});
Create a new account:
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);
}
Sign in:
try {
final credential = await FirebaseAuth.instance.signInWithEmailAndPassword(
email: emailAddress,
password: password,
);
} on FirebaseAuthException catch (e) {
final String error = switch (e.code) {
'invalid-email' => 'The email address is not valid.',
// Email enumeration protection (default since Sep 2023) reports
// 'invalid-credential' instead of 'user-not-found' / 'wrong-password';
// older native SDKs report 'invalid-login-credentials'. Handle all four so
// the app works with the setting on or off.
'invalid-credential' ||
'invalid-login-credentials' ||
'user-not-found' ||
'wrong-password' =>
'The email address or password is incorrect.',
'user-disabled' => 'This account has been disabled.',
'too-many-requests' ||
'network-request-failed' =>
'Something went wrong. Try again later.',
// Always keep a default branch — codes differ by platform and SDK version
// (for example Windows reports 'unknown-error' for a wrong password).
_ => 'Sign-in failed (${e.code}).',
};
print(error);
}
user-not-found and wrong-password with invalid-credential. Manage this in the Firebase console under Authentication > Settings.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.Share authentication state between Apple apps:
On Apple platforms, share auth state between apps in the same developer account by storing it in a shared Keychain access group. Enable the Keychain Sharing capability for each app with the same access group, then configure Firebase Auth with the fully qualified access group:
await FirebaseAuth.instance.setSettings(
userAccessGroup: 'TEAMID.com.example.group1',
);
Switching from the default Keychain to a shared access group signs out the existing user unless migrated. Pass migrateCurrentUser: true (requires a non-null userAccessGroup) to preserve the current session, including an anonymous one — safe to call at app startup before any other Auth call, since it reads the existing session straight from the Keychain before Auth restores it. Migration can overwrite a user already stored in the destination access group.
Google Sign-In (native platforms):
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 Sign-In (web):
Future<UserCredential> signInWithGoogle() async {
GoogleAuthProvider googleProvider = GoogleAuthProvider();
googleProvider.addScope('https://www.googleapis.com/auth/contacts.readonly');
googleProvider.setCustomParameters({'login_hint': 'user@example.com'});
return await FirebaseAuth.instance.signInWithPopup(googleProvider);
}
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.email and name scopes to present the full first-time sign-in UI (including "Share/Hide email"):```dart
final appleProvider = AppleAuthProvider();
appleProvider.addScope('email');
appleProvider.addScope('name');
```
revokeTokenWithAuthorizationCode() with the authorization code from userCredential.additionalUserInfo?.authorizationCode.revokeAccessToken() with the access token from userCredential.credential?.accessToken.```dart
// 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);
}
```
Before using phone authentication, ensure platform-specific prerequisites are met:
Phone number sign-in is only supported on real devices and the web. Testing on device emulators is not supported.
iOS: recaptcha-sdk-not-linked error
On 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:
projects.updateConfig REST API (set recaptchaConfig.phoneEnforcementState to OFF and recaptchaConfig.useSmsTollFraudProtection to false). See the official steps. This reduces fraud protection — prefer linking the SDK.uni_links/app_links or application:openURL: in the iOS runner.try-catch with FirebaseAuthException.e.code, never on e.message — messages come from the native SDKs, differ between platforms, and change between SDK versions.unknown-error, no-signed-in-user, account-exists-with-different-credentials). Check the error code reference when targeting several platforms.FirebaseAuthMultiFactorException by type, before FirebaseAuthException, and complete sign-in with e.resolver; its code differs by platform (second-factor-required vs multi-factor-auth-required).signInWithRedirect, getRedirectResult and reauthenticateWithPopup are the exception — they throw a plain FirebaseAuthException carrying the multi-factor code, without a resolver. Branch on the code in those flows, or enrolled users are stranded on a generic error with no way to complete the second factor.verifyPhoneNumber does not throw — errors arrive in the verificationFailed callback.account-exists-with-different-credential by signing the user in with the provider their account already uses, then calling linkWithCredential(e.credential). With email enumeration protection enabled, Firebase does not reveal which provider that is, so let the user choose.too-many-requests with retry logic or user feedback.operation-not-allowed by ensuring the provider is enabled in the Firebase console.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.// 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("newemail@example.com");
verifyBeforeUpdateEmail() — not updateEmail() — to change a user's email. The email only updates after the user verifies it.linkWithCredential() to connect multiple auth providers to a single account.FirebaseAuth.instance.signOut() when users exit the app.reauthenticateWithCredential().auth variable to get the signed-in user's UID for access control.Security warning: Avoid SMS-based MFA. SMS is insecure and easy to compromise or spoof.
Platform limitation: Windows does not support MFA. MFA with multiple tenants is not supported on Flutter.
Important: Firebase Dynamic Links is deprecated for email link authentication. Firebase Hosting is now used to send sign-in links.
handleCodeInApp: true in ActionCodeSettings — sign-in must always be completed in the app.SharedPreferences) when sending the sign-in link.