firebase / flutterfire

🔥 A collection of Firebase plugins for Flutter apps.
https://firebase.google.com/docs/flutter/setup
BSD 3-Clause "New" or "Revised" License
8.63k stars 3.95k forks source link

[Firebase_auth] : Email Link Sign-In doesn't work on Android #4711

Closed wolframm closed 3 years ago

wolframm commented 3 years ago

I followed these instructions on firebase.flutter.dev to implement email-link auth in my Flutter app. The mixture of Flutter-specific instructions with links to Firebase generic instructions is quite confusing. After spending multiple days, email-link sign in is still not working.

I successfully implemented email & password auth as well as Facebook auth using FlutterFire. My app is also successfully communicating with Firebase Firestore and Functions. I assume my project is setup correctly. I also implemented email link sign-in for my Firebase project's AngularFire web app without any issues.

Here is the method in my Flutter app that calls Firebase Auth:

Future<void> _signInWithEmailLink() async {
    final acs = ActionCodeSettings(
        url: "https://rebait.page.link/2irR",
        handleCodeInApp: true,
        iOSBundleId: "io.consumer.rebait.app",
        androidPackageName: "app.rebait.consumer",
        androidInstallApp: true,
        androidMinimumVersion: "12");
    try {
      await _auth.sendSignInLinkToEmail(email: _emailController.text, actionCodeSettings: acs);
      await _showEmailLinkSentDialog();
    } catch (err) {
      print('SIGN IN LINK ERROR');
      print(err.toString());
      return;
    }
    // Navigator.of(context).popUntil((route) => route.isFirst);
  }

Here is the function that is waiting to handle the sign-in link:

FirebaseDynamicLinks.instance.onLink(onSuccess: (PendingDynamicLinkData dynamicLink) async {
      print('ON LINK');
      print(dynamicLink.link);
      print(dynamicLink.link.data);
      if (FirebaseAuth.instance.isSignInWithEmailLink(dynamicLink.link.toString())) {
      if (FirebaseAuth.instance.isSignInWithEmailLink(dynamicLink.toString())) {
        print('IS SIGN IN LINK');
      } else {
        print('IS NOT SIGN IN LINK');
      }
    }, onError: (OnLinkErrorException e) async {
      print(e.message);
      _showErrorDialog(e);
    });

My app manages to call Firebase and an email is sent to my inbox with a sign-in link. When I click on the link in the Gmail app on my Android emulator, my app is moved to the foreground and it handles the link. The prints from onLink are:

I/flutter (22864): ON LINK
I/flutter (22864): https://rebait.page.link
I/flutter (22864): null
I/flutter (22864): IS NOT SIGN IN LINK

My dynamic link settings are as follows. However, I don't really understand what the purpose of this is. Doesn't Firebase Auth generate the necessary deep link data based on the ActionCodeSetting automatically (it does for AngularFire)? Not sure why I have to setup some dynamic link manually in the console.

Screen Shot 2021-01-18 at 11 41 34 AM

Firebase Auth settings:

Screen Shot 2021-01-18 at 11 43 14 AM Screen Shot 2021-01-18 at 11 43 30 AM

Flutter doctor output:

[✓] Flutter (Channel stable, 1.22.5, on macOS 11.1 20C69 darwin-x64, locale en-US)
    • Flutter version 1.22.5 at /Users/arnewolframm/flutter
    • Framework revision 7891006299 (6 weeks ago), 2020-12-10 11:54:40 -0800
    • Engine revision ae90085a84
    • Dart version 2.10.4

[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
    • Android SDK at /Users/arnewolframm/Library/Android/sdk
    • Platform android-30, build-tools 30.0.2
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 12.3)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 12.3, Build version 12C33
    • CocoaPods version 1.10.0.rc.1

[!] Android Studio (version 4.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)

[✓] Connected device (1 available)
    • sdk gphone x86 (mobile) • emulator-5554 • android-x86 • Android 11 (API 30) (emulator)
    ! Error: Arne’s iPhone is not connected. Xcode will continue when Arne’s iPhone is connected. (code -13)

Pubspec yaml:

name: consumer
description: Rebait Consumer App

version: 1.0.0+1

environment:
  sdk: ">=2.10.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter

  cupertino_icons: ^1.0.0

  firebase_core: ^0.7.0

  firebase_analytics: ^7.0.0

  firebase_auth: ^0.20.0+1
  cloud_firestore: ^0.16.0
  cloud_functions: ^0.9.0

  google_sign_in: ^4.4.4
  provider: ^4.1.1
  firebase_dynamic_links: ^0.7.0
  flutter_signin_button: ^1.0.0
  email_validator: ^1.0.0
  package_info: '>=0.4.0 <2.0.0'
  flutter_facebook_auth: 0.2.4
  rxdart: ^0.25.0
  google_fonts: ^1.1.0
  wakelock: ^0.2.1
  json_annotation: ^3.1.1
  http: ^0.12.2
  geolocator: ^6.0.0

dev_dependencies:
  build_runner: ^1.10.1
  json_serializable: ^3.5.1

  flutter_test:
    sdk: flutter

flutter_intl:
  enabled: true
  localizely:
    project_id: [...]

PLEASE HELP!

halildurmus commented 3 years ago

I came across this issue today. After hours of research and debugging, I've decided to use earlier versions of Firebase packages because 3 days ago, I upgraded all of my Firebase packages and thought that maybe this was the source of this problem.

So, in my pubspec.yaml file, I had the following Firebase packages installed. With these versions, the Passwordless Sign In wasn't working. firebase_auth: ^0.20.0+1 firebase_core: ^0.7.0 firebase_dynamic_links: ^0.7.0+1

After downgrading, now I have the following versions installed and the Passwordless Sign In is working. firebase_auth: ^0.18.4+1 firebase_core: ^0.5.3 firebase_dynamic_links: ^0.6.3

wolframm commented 3 years ago

@rrousselGit Do you have any suggestions?

sunil90210 commented 3 years ago

Observations:

  1. With firebase_auth: ^0.18.4+1, link received in email is a valid "page.link" which opens app correctly.
  2. With firebase_auth: ^0.20.0+1, link received in email is not a dynamic link, it opens in webpage and redirects to given URL within browser. 0.18.4+1 is the best we have for email link login now.
Toolenaar commented 3 years ago

Observations:

  1. With firebase_auth: ^0.18.4+1, link received in email is a valid "page.link" which opens app correctly.
  2. With firebase_auth: ^0.20.0+1, link received in email is not a dynamic link, it opens in webpage and redirects to given URL within browser. 0.18.4+1 is the best we have for email link login now.

Is this intended behaviour? I am having similar problems. Our login with email link stopped working aswell.

sunil90210 commented 3 years ago

Observations:

  1. With firebase_auth: ^0.18.4+1, link received in email is a valid "page.link" which opens app correctly.
  2. With firebase_auth: ^0.20.0+1, link received in email is not a dynamic link, it opens in webpage and redirects to given URL within browser. 0.18.4+1 is the best we have for email link login now.

Is this intended behaviour? I am having similar problems. Our login with email link stopped working aswell.

No, it's not intended behaviour. Latest is broken for email link auth.

darshankawar commented 3 years ago

@wolframm Are you still facing the issue ? Also, is your issue about email link sign-in not working or about confusion as how exactly to implement email link sign-in or document not being clear about it ?

Can you also try the official firebase_auth plugin example and check if using email with sign in link functionality works using it ?

wolframm commented 3 years ago

@darshankawar I don't see my job as debugging FlutterFire libs. So, I will work on other stuff for a couple of days and see if FlutterFire gets an update. If not, I will downgrade to firebase_auth 0.18.4+1, as suggested by @halildurmus and @sunil90210.

darshankawar commented 3 years ago

@wolframm I wasn't suggesting to debug the library but just to try out the official example and see if you get similar behavior using it, since the example also uses the latest version of the plugin.

martyfuhry commented 3 years ago

I also had this issue and fiddled around with the library for a few hours. In iOS, it seems like the email is sent with a properly formatted dynamic link, and the library works fine. In Android, though, it's completely busted.

In FlutterFirebaseAuthPlugin.java, the function sendSignInLinkToEmail does not properly use the ActionCodeSettings sent from the Flutter library. I had to manually create my own ActionCodeSettings by using the newBuilder() function and construct a hard-coded ActionCodeSettings object. This, of course, ignored the ActionCodeSettings set from the Flutter application, but that was OK for me since I was able to hard-code it to my needs. Once I was using my own hard-coded ActionCodeSettings, the email was sent off properly and used a correctly-formatted dynamic link.

This suggests that the underlying issue is in the function getActionCodeSettings or else in the Flutter library function formatting and passing the ActionCodeSettings to the Android library.

markusaksli-nc commented 3 years ago

I was able to reproduce this on the latest master 1.26.0-13.0.pre.179 with

  firebase_core: ^0.7.0
  firebase_auth: ^0.20.0+1
  firebase_dynamic_links: ^0.7.0+1
flutter doctor -v ```console [✓] Flutter (Channel master, 1.26.0-13.0.pre.179, on macOS 11.1 20C69 darwin-arm, locale en-GB) • Flutter version 1.26.0-13.0.pre.179 at /Users/nevercode/development/flutter_master • Framework revision fc9addb88b (42 minutes ago), 2021-01-26 03:24:03 -0500 • Engine revision f47ab4434d • Dart version 2.12.0 (build 2.12.0-257.0.dev) [✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3) • Android SDK at /Users/nevercode/Library/Android/sdk • Platform android-30, build-tools 30.0.3 • Java binary at: /Users/nevercode/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/201.7042882/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS • Xcode at /Applications/Xcode.app/Contents/Developer • Xcode 12.3, Build version 12C33 • CocoaPods version 1.10.1 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] Android Studio (version 4.1) • Android Studio at /Users/nevercode/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/201.7042882/Android Studio.app/Contents • Flutter plugin can be installed from: � https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: � https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495) [✓] Connected device (3 available) • Nevercode’s iPhone (mobile) • b668e524315069f3db3661ac11ff1f66afafebdb • ios • iOS 14.3 • macOS (desktop) • macos • darwin-arm64 • macOS 11.1 20C69 darwin-arm • Chrome (web) • chrome • web-javascript • Google Chrome 88.0.4324.96 • No issues found! ```

This is the code used to handle the link (not sure why this isn't in the official example, might make a PR for this).

Dynamic link handling This is a global variable that gets set after the link is successfully sent. ```dart String linkEmail; ``` This needs to be in `initState` or somewhere. ```dart FirebaseDynamicLinks.instance.onLink(onSuccess: (PendingDynamicLinkData dynamicLink) async { final Uri deepLink = dynamicLink?.link; if (deepLink != null) { if (linkEmail == null) { print("Could not sign in using email link because the email is null. Resend the link or implement a TextField to manually set the email"); return; } try { if (_auth.isSignInWithEmailLink(deepLink.toString())) { var user = (await _auth.signInWithEmailLink(email: linkEmail, emailLink: deepLink.toString())).user; print("Signed in $linkEmail using email sign-in link as ${user.uid}"); } else { print("$deepLink is not a valid email sign-in link"); } } catch (e) { print("Encountered error processing email sign-in link:\n$e"); } } }, onError: (OnLinkErrorException e) async { print('onLinkError'); print(e.message); }); ```

The findings of @martyfuhry seem to be correct. The link generated on Android doesn't work on either Android or iOS.

However, I can't get links generated on either platform to work on Android. The link generated on iOS seems to just go through an extra redirect in the browser (to the continue URL) and then just opens that in the app. The link generated on Android just goes directly to the continue URL and doesn't work either. In both cases the result on Android is:

I/flutter (27832): [my continueURL] is not a valid email sign-in link

On iOS everything works fine if you complete the necessary setup in Xcode.

abhaysood commented 3 years ago

Facing the same issue.

Using the following versions:

  firebase_core: ^0.7.0
  firebase_auth: ^0.20.0+1
  firebase_dynamic_links: ^0.7.0+1

The onSuccess or onError callbacks are not getting triggered after upgrading.

FirebaseDynamicLinks.instance.onLink(onSuccess: (PendingDynamicLinkData dynamicLink) async { ... }
tstrg commented 3 years ago

I'm facing the same issue, Flutter 1.25.0-8.3.pre • channel beta

  firebase_auth: ^0.20.0+1
  firebase_dynamic_links: ^0.7.0+1
  firebase_core: ^0.7.0

The onSuccess is getting triggered but the path and query params are empty so the Sign-In Email Link is not valid

dynamicLink?.link?.path is empty
dynamicLink?.link?.host is set to my domain
dynamicLink?.link?.queryParameters is empty
danReynolds commented 3 years ago

@russellwheatley Do you know if this is a fix we need to make in firebase_auth? If so, any idea which commit broke it? Been trying to hunt it down

jyardin commented 3 years ago

Following up the post of @martyfuhry, I think the fix lies in the ActionCodeSettings.asMap method (in action_code_settings.dart). Everywhere it is consumed (Android/iOS/Web), it is assumed that the map looks like this:

{
  'url': url,
  'dynamicLinkDomain': dynamicLinkDomain,
  'handleCodeInApp': handleCodeInApp,
  'iOS': {
    'bundleId': iOSBundleId,
  },
  'android': {
    'packageName': androidPackageName,
    'minimumVersion': androidMinimumVersion,
    'installApp': androidInstallApp,
  }
}

whereas the actual ActionCodeSettings mapping method is like this:

  Map<String, dynamic> asMap() {
    return <String, dynamic>{
      'url': url,
      'dynamicLinkDomain': dynamicLinkDomain,
      'handleCodeInApp': handleCodeInApp,
      'androidPackageName': androidPackageName,
      'androidMinimumVersion': androidMinimumVersion,
      'androidInstallApp': androidInstallApp,
      'iOSBundleId': iOSBundleId,
    };
  }

Changing the map method to the following fixed it for my app:

  Map<String, dynamic> asMap() {
    return <String, dynamic>{
      'url': url,
      'dynamicLinkDomain': dynamicLinkDomain,
      'handleCodeInApp': handleCodeInApp,
      'iOS': {
        'bundleId': iOSBundleId,
      },
      'android': {
        'packageName': androidPackageName,
        'minimumVersion': androidMinimumVersion,
        'installApp': androidInstallApp,
      }
    };
  }
}

I only tested it on Android, but If anyone need a quick fix, I implemented it here: https://github.com/jyardin/flutterfire/tree/email-link-signin-fix You can directly use it by adding the following to your pubspec overrides:

dependency_overrides:
  firebase_auth_platform_interface:
    git:
      url: git://github.com/jyardin/flutterfire.git
      ref: email-link-signin-fix
      path: packages/firebase_auth/firebase_auth_platform_interface/

It is based on version 0.20.0+1 of firebase_auth package (so you must be on this version).

jonsaw commented 3 years ago

Tested on iOS and Android with firebase_auth package: ^0.20.0+1. Looks like it's working! 🎉

Following up the post of @martyfuhry, I think the fix lies in the ActionCodeSettings.asMap method (in action_code_settings.dart). Everywhere it is consumed (Android/iOS/Web), it is assumed that the map looks like this:

{
  'url': url,
  'dynamicLinkDomain': dynamicLinkDomain,
  'handleCodeInApp': handleCodeInApp,
  'iOS': {
    'bundleId': iOSBundleId,
  },
  'android': {
    'packageName': androidPackageName,
    'minimumVersion': androidMinimumVersion,
    'installApp': androidInstallApp,
  }
}

whereas the actual ActionCodeSettings mapping method is like this:

  Map<String, dynamic> asMap() {
    return <String, dynamic>{
      'url': url,
      'dynamicLinkDomain': dynamicLinkDomain,
      'handleCodeInApp': handleCodeInApp,
      'androidPackageName': androidPackageName,
      'androidMinimumVersion': androidMinimumVersion,
      'androidInstallApp': androidInstallApp,
      'iOSBundleId': iOSBundleId,
    };
  }

Changing the map method to the following fixed it for my app:

  Map<String, dynamic> asMap() {
    return <String, dynamic>{
      'url': url,
      'dynamicLinkDomain': dynamicLinkDomain,
      'handleCodeInApp': handleCodeInApp,
      'iOS': {
        'bundleId': iOSBundleId,
      },
      'android': {
        'packageName': androidPackageName,
        'minimumVersion': androidMinimumVersion,
        'installApp': androidInstallApp,
      }
    };
  }
}

I only tested it on Android, but If anyone need a quick fix, I implemented it here: https://github.com/jyardin/flutterfire/tree/email-link-signin-fix You can directly use it by adding the following to your pubspec overrides:

dependency_overrides:
  firebase_auth_platform_interface:
    git:
      url: git://github.com/jyardin/flutterfire.git
      ref: email-link-signin-fix
      path: packages/firebase_auth/firebase_auth_platform_interface/

It is based on version 0.20.0+1 of firebase_auth package (so you must be on this version).

danReynolds commented 3 years ago

Awesome! I'll try it out. Wouldn't this break iOS since it was working before and we've changed it from the prefixed action code settings version to the map?

danReynolds commented 3 years ago

Confirmed @jonsaw's fix works for both iOS and Android. Thanks @jonsaw !!!

jonsaw commented 3 years ago

@danReynolds credit goes to @jyardin 😄

woutervanwijk commented 3 years ago

Observations:

1. With firebase_auth: ^0.18.4+1, link received in email is a valid "page.link" which opens app correctly.

2. With firebase_auth: ^0.20.0+1, link received in email is not a dynamic link, it opens in webpage and redirects to given URL within browser.
   0.18.4+1 is the best we have for email link login now.

I had to revert back too. This kind of bugs really shouldn't happen in such a project!

Arrowsome commented 3 years ago

Ah man I worked on Email Link Auth for a whole day and thought It's my fault not working. My project is currently on hold until this gets fixed. When would a new version be released?

bartonhammond commented 3 years ago

I'm having same problem. Is the best solution to fall back to ^0.18.4+1 or use the solution from @jyardin ?