With firebase_auth in Flutter, how do you sign a user in with signInWithEmailAndPassword and handle the FirebaseAuthException it can throw?
answer
- two required named parameters
- returns a UserCredential
- on FirebaseAuthException catch, switch on code
- invalid-credential replaced two old codes
- let the auth listener navigate
basics
~10 sAwait FirebaseAuth.instance.signInWithEmailAndPassword(email:, password:) inside try, catch FirebaseAuthException and switch on e.code. With email enumeration protection, wrong email or password both arrive as invalid-credential, not user-not-found or wrong-password.
solid answer
~40 s`signInWithEmailAndPassword(email:, password:)` returns a `UserCredential` and, on success, pushes the new `User` to every `authStateChanges()` listener — so the button handler should only sign in and report errors, leaving navigation to the listener. Failures throw `FirebaseAuthException`, which extends `FirebaseException`; catch it with `on FirebaseAuthException catch (e)` and branch on `e.code`: `invalid-email`, `user-disabled`, `too-many-requests`, `network-request-failed`, `operation-not-allowed` when the provider is not enabled, and `invalid-credential` for a wrong email or password. The old `user-not-found` and `wrong-password` codes are deprecated: with email enumeration protection, the default for new projects since September 2023, both collapse into `invalid-credential`. Always keep a fallback branch for codes you did not list.
code
dart · 24 linesimport 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
Future<void> onSignInPressed(
BuildContext context,
String email,
String password,
) async {
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: email.trim(),
password: password,
);
} on FirebaseAuthException catch (e) {
final String text = switch (e.code) {
'invalid-credential' => 'Email or password is incorrect.',
'user-disabled' => 'This account has been disabled.',
'too-many-requests' => 'Too many attempts. Try again later.',
_ => 'Sign-in failed (${e.code}).',
};
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(text)));
}
}go deeper
Write the try/on FirebaseAuthException block, pass both required named parameters, and show a readable message per error code.
Explain that success updates the auth streams, so navigation belongs to the listener, and why invalid-credential replaced user-not-found and wrong-password.
Audit old error handling that still branches on deprecated codes, keep a fallback for unknown codes, and guard BuildContext after the await.
Set the product rule for how much a sign-in screen may reveal about account existence, and keep client messaging aligned with the backend's anti-enumeration stance.
## The call `firebase_auth` signs a user in with an email address and password through one method on `FirebaseAuth`: ```dart Future<UserCredential> signInWithEmailAndPassword({ required String email, required String password, }) ``` Both parameters are required named parameters. The returned `UserCredential` carries the `user` (a `User?`), plus `additionalUserInfo` and, for some providers, a `credential`. Creating an account uses the sibling method `createUserWithEmailAndPassword` with the same two parameters. Before either works, the **Email/Password** provider has to be enabled for the project in the Firebase console; otherwise the call fails with `operation-not-allowed`. On success, the method also updates the SDK's signed-in state, so every `authStateChanges()`, `idTokenChanges()` and `userChanges()` listener receives the new `User`. A well-structured app therefore **does not navigate from the sign-in button's handler**: the handler only signs in and reports errors, and the listener that already drives routing moves the user to the recipe feed. ## The exception type Failures arrive as a **`FirebaseAuthException`**, a subclass of `firebase_core`'s `FirebaseException`. It always has a `code` and usually a `message`; some failures also fill `email`, `credential`, `phoneNumber` or `tenantId`. Catch it with an `on` clause and branch on `code`: ```dart import 'package:firebase_auth/firebase_auth.dart'; Future<String?> signIn(String email, String password) async { try { await FirebaseAuth.instance.signInWithEmailAndPassword( email: email.trim(), password: password, ); return null; // the auth listener handles navigation } on FirebaseAuthException catch (e) { return switch (e.code) { 'invalid-email' => 'That email address is not valid.', 'invalid-credential' => 'Email or password is incorrect.', 'user-disabled' => 'This account has been disabled.', 'too-many-requests' => 'Too many attempts. Try again later.', 'network-request-failed' => 'No connection. Check your network.', _ => 'Sign-in failed (${e.code}).', }; } } ``` ## The codes that matter — and the two that no longer appear The pinned `signInWithEmailAndPassword` documentation lists these codes: | Code | Meaning | |---|---| | `invalid-email` | The address is malformed | | `user-disabled` | The account was disabled | | `invalid-credential` | Email or password is wrong | | `too-many-requests` | Too many attempts; the backend is throttling | | `user-token-expired` | The session's refresh token has expired | | `network-request-failed` | No network, or the request failed in transit | | `operation-not-allowed` | Email/Password sign-in is not enabled | | `user-not-found`, `wrong-password` | **Deprecated** — not returned on projects with email enumeration protection | The trap is the last row. Tutorials written before late 2023 branch on `user-not-found` and `wrong-password` to show "no such account" versus "wrong password". The source notes that **email enumeration protection** is the default for new projects since September 2023, and on such projects both cases collapse into `invalid-credential` — deliberately, so an attacker cannot probe which addresses have accounts. The emulator may report it as `INVALID_LOGIN_CREDENTIALS`. Code that only checks the two old codes falls through to its generic branch for every bad password. ## Good habits around the call - **Trim the email, never the password.** Whitespace in a password is a legitimate character. - **Show one message for bad credentials.** Matching the backend's anti-enumeration stance, the UI should not claim to know whether the account exists. - **Keep the fallback branch.** Codes are strings and the list evolves; an unknown code should still produce a readable message, ideally with the code logged. - **Guard `BuildContext` after the await.** If the handler shows a `SnackBar` after the call, check `context.mounted` first, since the listener may already have replaced the screen. - **Do not persist the password or the tokens yourself.** The SDK keeps the session; your own token storage is a separate concern. ## The handler, step by step 1. Validate the form locally — an empty field never needs a network call. 2. Disable the button, so a double tap cannot start two sign-ins. 3. Await `signInWithEmailAndPassword` inside `try`. 4. On `FirebaseAuthException`, map `e.code` to one user-facing message and re-enable the button. 5. On success, do nothing further; the auth listener replaces the screen. ## What the call does not do It does not verify the address — `sendEmailVerification()` on the `User` does that, and `user.emailVerified` reports the result. It does not route. And it does not throw on an unverified email: whether an unverified account may use the app is a product rule the app enforces itself, not an error the sign-in call raises.
- Why can't the sign-in screen tell a user whether the email has no account?On projects with email enumeration protection — the default for new projects since September 2023 — both an unknown email and a wrong password return `invalid-credential`. The backend withholds the distinction on purpose so attackers cannot test which addresses are registered, and the UI should match with one message.
- Should the sign-in handler navigate to the home screen after the await succeeds?Preferably not. A successful sign-in already emits the new `User` on `authStateChanges()`, and the listener that decides between sign-in and home should make the move. Navigating from the handler as well duplicates the decision and can push the home screen twice.
saying these in an interview costs you the question
- Check e.code == 'wrong-password' to detect a bad password on a current project.
- signInWithEmailAndPassword returns null instead of throwing when credentials are wrong.
- Catch PlatformException, since all Firebase errors come through platform channels.
- The sign-in call works without enabling the Email/Password provider in the console.
- Trim both the email and the password before calling sign-in.