简体   繁体   中英

Flutter: have different authentication providers in Firebase

I have different methods in my app to log in:

  • Facebook
  • Google
  • Apple
  • Email

For the question I'll focus on the first 2 ones. When the user logs in with Facebook the providers look like this:

在此处输入图像描述

That's fine but if I log out and log in again, this time with a new Google account but using same email, the providers look like this:

在此处输入图像描述

Now, if I log out and log in again with Facebook I face the account-exists-with-different-credential error. Something for which I have the logic prepared and show its provider login method, but this user should have both provider available and he should be able to log in with both methods.

This is my code:

Future facebookSignIn(BuildContext context) async {
    final LoginResult result = await FacebookAuth.instance.login();

    if (result.status == LoginStatus.success) {
      final AccessToken accessToken = result.accessToken!;
      AuthCredential credential =
          FacebookAuthProvider.credential(accessToken.token);
      await _firebaseCredential(context, credential);
    }
  }

 Future googleSignIn(BuildContext context,
      [String? email, facebookCredential]) async {
    try {
      GoogleSignInAccount googleUser;
      dynamic popup = await _googleSignIn.signIn();

      // cancelled login
      if (popup == null) {
        return null;
      }

      googleUser = popup;

      GoogleSignInAuthentication googleAuth = await googleUser.authentication;
      final AuthCredential credential = GoogleAuthProvider.credential(
        accessToken: googleAuth.accessToken,
        idToken: googleAuth.idToken,
      );
      await _firebaseCredential(context, credential);
    } on FirebaseAuthException catch (e) {
      // await FirebaseCrashlytics.instance.recordError(
      //   e,
      //   StackTrace.fromString("/googleSignIn"),
      //   reason: e.message,
      // );
      // return null;
    }
  }

 _firebaseCredential(BuildContext context, credential) async {
    try {
      User user =
          (await FirebaseAuth.instance.signInWithCredential(credential)).user!;
      // Provider.of<MyRents>(context, listen: false).updateUI();
      await firebaseProfile.updateUserData(context, user);
    } on FirebaseAuthException catch (error) {
      // final error = e as FirebaseAuthException;
      if (error.code == 'account-exists-with-different-credential') {
        String email = error.email!;
        // AuthCredential pendingCredential = e.credential;

        List<String> signInMethods =
            await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);

        // If the user has several sign-in methods,
        // the first method in the list will be the "recommended" method to use.
        if (signInMethods.first == 'google.com' ||
            signInMethods.first == 'facebook.com') {
          // TODO: fix facebook
          return await googleSignIn(context, email, credential);
        } else {
          ScaffoldMessenger.of(context)
              .showSnackBar(SnackBar(content: Text(error.message!)));
        }
      } else {
        ScaffoldMessenger.of(context)
            .showSnackBar(SnackBar(content: Text(error.message!)));
      }
    }
  }

Am I missing something?

flutter_facebook_auth: ^4.3.3
google_sign_in: ^5.2.1
Future googleSignIn(BuildContext context,
      [String? email, facebookCredential]) async {
    try {
      GoogleSignInAccount googleUser;
      dynamic popup = await _googleSignIn.signIn();

      // cancelled login
      if (popup == null) {
        return null;
      }

      googleUser = popup;

      GoogleSignInAuthentication googleAuth = await googleUser.authentication;
      final AuthCredential credential = GoogleAuthProvider.credential(
        accessToken: googleAuth.accessToken,
        idToken: googleAuth.idToken,
      );
      await _firebaseCredential(context, credential);
    } on FirebaseAuthException catch (e) {
      // await FirebaseCrashlytics.instance.recordError(
      //   e,
      //   StackTrace.fromString("/googleSignIn"),
      //   reason: e.message,
      // );
      // return null;
    }
  }

Future facebookSignIn(BuildContext context) async {
    final LoginResult result = await FacebookAuth.instance.login();

    if (result.status == LoginStatus.success) {
      final AccessToken accessToken = result.accessToken!;
      AuthCredential credential =
          FacebookAuthProvider.credential(accessToken.token);

      await _firebaseCredential(context, credential);
    }
  }

// other methods...

_firebaseCredential(BuildContext context, credential) async {
    try {
      User user =
          (await FirebaseAuth.instance.signInWithCredential(credential)).user!;
      await firebaseProfile.updateUserData(context, user);
    } on FirebaseAuthException catch (error) {
      if (error.code == 'account-exists-with-different-credential') {
        String email = error.email!;
        List<String> signInMethods =
            await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
        // bool newUser = (signInMethods.length > 0) ? false : true;

        // If the user has several sign-in methods,
        // the first method in the list will be the "recommended" method to use.
        var user;
        switch (signInMethods.first) {
          case 'google.com':
            user = await googleSignIn(context, email, credential);
            break;
          case 'facebook.com':
            user = await facebookSignIn(context);
            break;
          case 'apple.com':
            user = await appleSignIn(context);
            break;
          case 'password':
            // since password is managed by user we force have email provider only
            ScaffoldMessenger.of(context).showSnackBar(SnackBar(
                content: Text(translate('auth.signInMethods_password'))));
            break;
          // TODO: apple
        }
        await linkProvider(context, credential);
        return user;
      }

      return ScaffoldMessenger.of(context)
          .showSnackBar(SnackBar(content: Text(error.message!)));
    }
  }

  // just some extra error covering
  Future linkProvider(BuildContext context, credential) async {
    try {
      await FirebaseAuth.instance.currentUser?.linkWithCredential(credential);
    } on FirebaseAuthException catch (e) {
      switch (e.code) {
        case "provider-already-linked":
          ScaffoldMessenger.of(context).showSnackBar(SnackBar(
              content: Text(translate('auth.provider_already_linked'))));
          break;
        case "invalid-credential":
          ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text(translate('auth.invalid_credential'))));
          break;
        case "credential-already-in-use":
          ScaffoldMessenger.of(context).showSnackBar(SnackBar(
              content: Text(translate('auth.credential_already_in_use'))));
          break;
        default:
          ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text(translate('auth.something_happened'))));
      }
    }
  }

If you do Google -> Facebook it will look like this:

在此处输入图像描述

Other way around only Google will be present if your Google email is a trusted email (gmail). More info about that: https://groups.google.com/g/firebase-talk/c/ms_NVQem_Cw/m/8g7BFk1IAAAJ

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM