Search code examples
flutterparse-platformparse-serverparse-cloud-code

How to implement sign in methods merging in flutter app using parse server?


in my mobile app i have 4 methods of login:

  1. Normal sign in/log in using e-mail and password
  2. Google Sign In
  3. Flutter Facebook Auth
  4. Sign In With Apple

The problem is for example when I create my account using Google Sign In with [email protected], then i log out and try to login using my Facebook account which is created with the same e-mail there's error. Summing up every single login function works independently.

I wonder if it's possible to handle this with cloud function?

Here's Flutter code:

 ///GOOGLE
  static Future<bool> signWithGoogle() async {
    // return false;
    final GoogleSignIn _googleSignIn = GoogleSignIn( scopes: ['email'] );

    GoogleSignInAccount? account = await _googleSignIn.signIn();
    googleSignInAccount = account;
    if (account == null) return false;
    GoogleSignInAuthentication authentication = await account.authentication;

    String? email = _googleSignIn.currentUser!.email;

    final bool firstLogin = await isFirstLogin(email);
    ParseResponse response;

    if (firstLogin){
      response = await ParseUser.loginWith(
        'google',
        google(
            authentication.accessToken!,
            _googleSignIn.currentUser!.id,
            authentication.idToken!
        ),
        username: _googleSignIn.currentUser!.displayName,
        email: email,
      );
    }else {
      response = await ParseUser.loginWith(
        'google',
        google(
            authentication.accessToken!,
            _googleSignIn.currentUser!.id,
            authentication.idToken!
        ),
      );
    }


    if (response.success){
      return _setDeviceId(response.result);
    }else
      _handleError(response);

    return false;
  }

  static Future<GoogleSignInAccount?> googleForDataOnly() async {
    final GoogleSignIn _googleSignIn = GoogleSignIn( scopes: ['email'] );

    return await _googleSignIn.signIn();
  }

  ///FACEBOOK
  static Future<bool> signWithFacebook() async {
    final LoginResult result = await FacebookAuth.instance.login(permissions: [
      'email',
      'public_profile',
      'user_location'
    ]);
    if (result.status == LoginStatus.cancelled) return false;

    if (result.status == LoginStatus.success) {
      if (result.accessToken == null) {
        Fluttertoast.showToast(msg: Translate.unknownError());
        return false;
      }

      final userData = await FacebookAuth.instance.getUserData();
      String? email = userData['email'];

      if (email == null || email.isEmpty){
        Fluttertoast.showToast(msg: Translate.emailPermissionRequired());
        return false;
      }

      final bool firstLogin = await isFirstLogin(email);
      ParseResponse response;

      if (firstLogin){
        response = await ParseUser.loginWith(
          'facebook',
          facebook(result.accessToken!.token,
              result.accessToken!.userId,
              result.accessToken!.expires
          ),
          email: email,
          username: userData['name']
        );
      }else {
        response = await ParseUser.loginWith(
          'facebook',
          facebook(result.accessToken!.token,
              result.accessToken!.userId,
              result.accessToken!.expires
          ),
        );
      }

      if (response.success) return _setDeviceId(response.result);
      else _handleError(response);

    }

    return false;
  }

  static Future<bool> facebookDataOnly() async {
    final LoginResult result = await FacebookAuth.instance.login(permissions: [
      'email',
      'public_profile',
      'user_location'
    ]);
    if (result.status == LoginStatus.cancelled) return false;

    if (result.status == LoginStatus.success) {
      if (result.accessToken == null) {
        Fluttertoast.showToast(msg: Translate.unknownError());
        return false;
      }

     return true;
    }

    return false;
  }

  ///APPLE
  static Future<bool> signWithApple() async {
    final AuthorizationCredentialAppleID credential = await SignInWithApple.getAppleIDCredential(
      scopes: [AppleIDAuthorizationScopes.email, AppleIDAuthorizationScopes.fullName],
    );

    final String? email = credential.email;
    final String? name = credential.givenName;
    final String? famName = credential.familyName;
    String? userName;
    if (name != null && famName != null){
      userName = '$name $famName';
    }

    if (email == null) {
      Fluttertoast.showToast(msg: Translate.invalidEmail());
      return false;
    }

    final bool firstLogin = await isFirstLogin(email);
    print(firstLogin);
    ParseResponse response;

    if (firstLogin){
      response = await ParseUser.loginWith(
        'apple',
        apple(
            credential.identityToken!,
            credential.userIdentifier!
        ),
        email: email,
        username: userName
      );
    }else {
      response = await ParseUser.loginWith(
        'apple',
        apple(
            credential.identityToken!,
            credential.userIdentifier!
        ),
      );
    }


    if (response.success){
      return _setDeviceId(response.result);
    }else {
      _handleError(response);
    }

    _sendLoginInfo(credential, response.success);

    return false;
  }

Solution

  • What you're missing is a functionality called linking users, I didn't find built-in function in Flutter SDK so I'm attaching docs from REST API.

    Parse allows you to link your users with services like Twitter and Facebook, enabling your users to sign up or log into your application using their existing identities. This is accomplished through the sign-up and update REST endpoints by providing authentication data for the service you wish to link to a user in the authData field. Once your user is associated with a service, the authData for the service will be stored with the user and is retrievable by logging in.

    You're supposed to populate authData attribute of user with login provider data in order to link it to the user. This can be done with a help of a cloud code or a simple http request from client. Let's say that you will integrate this in your flutter app the request for linking facebook login provider should be made of:

    # Use PUT http method
    curl -X PUT \
      # Add App ID to header
      -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
      # Add Client Key to header
      -H "X-Parse-Client-Key: ${CLIENT_KEY}" \
      # Add Session Token to header of the user you're linking the login provider to
      -H "X-Parse-Session-Token: r:samplei3l83eerhnln0ecxgy5" \
      # Add Contet-Type to header
      -H "Content-Type: application/json" \
      -d '{
            "authData": {
              "facebook": {
                "id": "123456789",
                "access_token": "SaMpLeAAibS7Q55FSzcERWIEmzn6rosftAr7pmDME10008bWgyZAmv7mziwfacNOhWkgxDaBf8a2a2FCc9Hbk9wAsqLYZBLR995wxBvSGNoTrEaL",
                "expiration_date": "2022-01-01T12:23:45.678Z"
              }
            }
          }' \
      # Call endpoint for the user you're linking the login provider to
      https://YOUR.PARSE-SERVER.HERE/parse/users/{user_object_id}