Skip to content

fix(auth, android): report an empty email or password as missing-email/missing-password instead of channel-error - #18876

Open
npateras wants to merge 4 commits into
firebase:mainfrom
npateras:firebase-auth-android-empty-credentials
Open

npateras wants to merge 4 commits into
firebase:mainfrom
npateras:firebase-auth-android-empty-credentials

Conversation

@npateras

@npateras npateras commented Oct 27, 2026 •

Copy link
Copy Markdown

Description

On Android, calling createUserWithEmailAndPassword, signInWithEmailAndPassword or sendPasswordResetEmail with an empty email or password fails with:

[firebase_auth/channel-error] Unable to establish connection on channel: "dev.flutter.pigeon.firebase_auth_platform_interface.FirebaseAuthHostApi.signInWithEmailAndPassword".

Empty input is the caller's mistake, and this PR does not change that. What's wrong is how the mistake gets reported. Every other platform returns a FirebaseAuthException with an auth error code. On Android the app gets an error that reads like a broken plugin installation, and the actual cause only shows up in logcat.

Why it happens

  • The Android SDK checks its arguments with Preconditions.checkNotEmpty and throws IllegalArgumentException("Given String is empty or null") synchronously. It throws before it returns a Task and before it sends any request. You can see this in the firebase-auth 25.0.0 bytecode for createUserWithEmailAndPassword, signInWithEmailAndPassword and both sendPasswordResetEmail overloads.
  • FlutterFirebaseAuthPlugin only handles failures inside addOnCompleteListener, so this exception escapes the host method.
  • Every FirebaseAuthHostApi method is @async. For async methods, Pigeon doesn't wrap the api.* call in try/catch (in GeneratedAndroidFirebaseAuth.g.kt, the only catch (exception: Throwable) is in the one synchronous handler).
  • The exception therefore reaches the engine's BasicMessageChannel.IncomingMessageHandler.onMessage. That method logs Failed to handle message and calls callback.reply(null). In messages.pigeon.dart, _extractReplyValueOrThrow turns a null reply into PlatformException(code: 'channel-error'), and convertPlatformException passes that through as FirebaseAuthException(code: 'channel-error').

#11931 shows the cost. The reporter tried flutter clean, pub upgrade and downgrading firebase_auth/firebase_core before a maintainer found Given String is empty or null in the stack trace. The issue was closed as user error, which is fair for the input itself. The plugin still surfaced the platform's validation error as a channel failure.

What this PR changes

The three host methods now catch the SDK's synchronous IllegalArgumentException and return it through the Pigeon callback, the same way the plugin reports every other SDK failure:

Input Before After
empty email channel-error missing-email: "An email address must be provided."
empty password channel-error missing-password: "A password must be provided."
any other IllegalArgumentException channel-error the existing parserExceptionToFlutter mapping

These codes and messages are the Android SDK's own ERROR_MISSING_EMAIL and ERROR_MISSING_PASSWORD entries (An email address must be provided. / A password must be provided.). The SDK simply never reaches them for empty input, because its precondition throws first. Like ERROR_INVALID_EMAIL, they reach Dart as missing-email and missing-password.

For comparison, the same input on other platforms:

  • iOS (FirebaseAuth/Sources/Swift/Auth/Auth.swift):
    • createUser: empty email gives missing-email, empty password gives weak-password.
    • signIn: empty password gives wrong-password, and an empty email goes to the backend.
  • Web: the JS SDK has no client-side check and sends the request. The Auth emulator then answers:
    • MISSING_EMAIL for signUp and for the password-reset sendOobCode
    • INVALID_EMAIL for signInWithPassword
    • MISSING_PASSWORD for signUp and signInWithPassword

So every other platform already returns a typed auth error here. Android was the outlier.

Scope

This PR is limited to the three email/password entry points where users actually run into this. The same synchronous checkNotEmpty precondition exists in other methods that the plugin calls the same way:

  • FirebaseAuth: applyActionCode, checkActionCode, confirmPasswordReset, verifyPasswordResetCode, fetchSignInMethodsForEmail, sendSignInLinkToEmail, signInWithCustomToken, revokeAccessToken
  • FirebaseUser: updatePassword, updateEmail, unlink

I'm happy to extend the fix to them here or in a follow-up if you'd like.

Tests

I added Android-only cases to example/integration_test/firebase_auth_instance_e2e_test.dart for all three methods, covering both empty email and empty password. They're skipped on the other platforms, which return different codes for the same input (see above). The Android cases fail before any request is sent, so they don't depend on the emulator's responses.

I ran flutter test integration_test/e2e_test.dart on an API 36 Android emulator against the Auth emulator (firebase-tools 15.34.1, Flutter 3.47.8, Firebase BoM 35.1.0):

  • On main: the three new cases fail with [firebase_auth/channel-error], and logcat shows BasicMessageChannel#…createUserWithEmailAndPassword Failed to handle message / java.lang.IllegalArgumentException: Given String is empty or null.
  • With this PR, filtered to the createUserWithEmailAndPassword, sendPasswordResetEmail() and signInWithEmailAndPassword() groups: 13 passed and 1 skipped (the existing Android skip), including the success paths.

Related Issues

Checklist

Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes ([x]).
This will ensure a smooth and quick review process. Updating the pubspec.yaml and changelogs is not required.

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • My PR includes unit or integration tests for all changed/updated/fixed behaviors (See Contributor Guide).
  • All existing and new tests are passing.
  • I updated/added relevant documentation (doc comments with ///).
  • The analyzer (melos run analyze) does not report any problems on my PR.
  • I read and followed the Flutter Style Guide.
  • I signed the CLA.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

Does your PR require plugin users to manually update their apps to accommodate your change?

  • Yes, this is a breaking change.
  • No, this is not a breaking change.

@google-cla

google-cla Bot commented Oct 27, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the Android Firebase Auth plugin to catch IllegalArgumentException when email or password is empty, mapping them to specific missing email/password error codes. It also adds integration tests to verify this behavior. The review feedback suggests replacing Platform.isAndroid with defaultTargetPlatform != TargetPlatform.android in the tests to prevent potential runtime errors on Web and maintain consistency.

},
// Only the Android SDK rejects empty values itself; elsewhere the
// error comes from the backend and differs per method.
skip: kIsWeb || !Platform.isAndroid,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

For consistency with other platform checks in this file (e.g., lines 1212 and 1275), and to avoid direct usage of Platform from dart:io which can throw runtime errors on Web if not guarded carefully, use defaultTargetPlatform != TargetPlatform.android instead of !Platform.isAndroid.

Suggested change
skip: kIsWeb || !Platform.isAndroid,
skip: kIsWeb || defaultTargetPlatform != TargetPlatform.android,

),
);
},
skip: kIsWeb || !Platform.isAndroid,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

For consistency with other platform checks in this file, use defaultTargetPlatform != TargetPlatform.android instead of !Platform.isAndroid.

Suggested change
skip: kIsWeb || !Platform.isAndroid,
skip: kIsWeb || defaultTargetPlatform != TargetPlatform.android,

),
);
},
skip: kIsWeb || !Platform.isAndroid,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

For consistency with other platform checks in this file, use defaultTargetPlatform != TargetPlatform.android instead of !Platform.isAndroid.

Suggested change
skip: kIsWeb || !Platform.isAndroid,
skip: kIsWeb || defaultTargetPlatform != TargetPlatform.android,

@SelaseKay SelaseKay left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @npateras, thanks for the PR. Kindly apply gemini review feedbacks. Other than that, LGTM!

@npateras

Copy link
Copy Markdown
Author

Applied Gemini's suggestions in 4134a71: the three Android-only skips now use defaultTargetPlatform. Thanks!

…id-empty-credentials

# Conflicts:
#	packages/firebase_auth/firebase_auth/example/integration_test/firebase_auth_instance_e2e_test.dart

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants