Authentication using Facebook at first and then Google causes an error in Firebase for Android

AndroidFirebaseFacebook LoginFirebase AuthenticationGoogle Authentication

Android Problem Overview


As I understand from the Firebase Docs, if a user authenticates his account with a credential, he should strictly login by using the same credential if the credential is not linked with another one yet.

In other words, if I create an account by using Google sign in, and then (after sign out) try to login with Facebook credential by using the same email that is used for Google credential, I should see this exception in logcat:

> "An account already exists with the same email address but different > sign-in credentials. Sign in using a provider associated with this > email address."

And yes, I get this exception unsurprisingly. But if I create an account by using Facebook, and then try to login with Google credential, the provider of this account (Facebook) is converted to Google. This time authentication does not fail but it is not the expected result. I want to associate each user with a specific credential in a way. How should I fix this? You can see the code below:

public class SignInActivity extends AppCompatActivity implements GoogleApiClient.OnConnectionFailedListener,
        View.OnClickListener {

    private static final String TAG = "SignInActivity";
    private static final int RC_SIGN_IN = 9001;

    private GoogleApiClient mGoogleApiClient;
    private FirebaseAuth mFirebaseAuth;
    private FirebaseAuth.AuthStateListener mFirebaseAuthListener;

    private CallbackManager mCallbackManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_sign_in);

        // Facebook Login
        FacebookSdk.sdkInitialize(getApplicationContext());
        mCallbackManager = CallbackManager.Factory.create();

        LoginButton mFacebookSignInButton = (LoginButton) findViewById(R.id.facebook_login_button);
        mFacebookSignInButton.setReadPermissions("email", "public_profile");

        mFacebookSignInButton.registerCallback(mCallbackManager, new FacebookCallback<LoginResult>() {
            @Override
            public void onSuccess(LoginResult loginResult) {
                Log.d(TAG, "facebook:onSuccess:" + loginResult);
                firebaseAuthWithFacebook(loginResult.getAccessToken());
            }

            @Override
            public void onCancel() {
                Log.d(TAG, "facebook:onCancel");
            }

            @Override
            public void onError(FacebookException error) {
                Log.d(TAG, "facebook:onError", error);
            }
        });

        // Google Sign-In
        // Assign fields
        SignInButton mGoogleSignInButton = (SignInButton) findViewById(R.id.google_sign_in_button);

        // Set click listeners
        mGoogleSignInButton.setOnClickListener(this);

        GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestIdToken(getString(R.string.default_web_client_id))
                .requestEmail()
                .build();
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .enableAutoManage(this /* FragmentActivity */, this /* OnConnectionFailedListener */)
                .addApi(Auth.GOOGLE_SIGN_IN_API, gso)
                .build();

        // Initialize FirebaseAuth
        mFirebaseAuth = FirebaseAuth.getInstance();

        mFirebaseAuthListener = new FirebaseAuth.AuthStateListener() {
            @Override
            public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
                FirebaseUser user = firebaseAuth.getCurrentUser();
                if (user != null) {
                    // User is signed in
                    Log.d(TAG, "onAuthStateChanged:signed_in:" + user.getUid());
                } else {
                    // User is signed out
                    Log.d(TAG, "onAuthStateChanged:signed_out");
                }
            }
        };
    }

    @Override
    public void onStart() {
        super.onStart();
        mFirebaseAuth.addAuthStateListener(mFirebaseAuthListener);
    }

    @Override
    public void onStop() {
        super.onStop();
        if (mFirebaseAuthListener != null) {
            mFirebaseAuth.removeAuthStateListener(mFirebaseAuthListener);
        }
    }

    private void firebaseAuthWithGoogle(GoogleSignInAccount acct) {
        Log.d(TAG, "firebaseAuthWithGooogle:" + acct.getId());
        AuthCredential credential = GoogleAuthProvider.getCredential(acct.getIdToken(), null);
        mFirebaseAuth.signInWithCredential(credential)
                .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
                    @Override
                    public void onComplete(@NonNull Task<AuthResult> task) {
                        Log.d(TAG, "signInWithCredential:onComplete:" + task.isSuccessful());

                        // If sign in fails, display a message to the user. If sign in succeeds
                        // the auth state listener will be notified and logic to handle the
                        // signed in user can be handled in the listener.
                        if (!task.isSuccessful()) {
                            Log.w(TAG, "signInWithCredential", task.getException());
                            Toast.makeText(SignInActivity.this, "Authentication failed.",
                                    Toast.LENGTH_SHORT).show();
                        } else {
                            startActivity(new Intent(SignInActivity.this, MainActivity.class));
                            finish();
                        }
                    }
                });
    }

    private void firebaseAuthWithFacebook(AccessToken token) {
        Log.d(TAG, "handleFacebookAccessToken:" + token);

        final AuthCredential credential = FacebookAuthProvider.getCredential(token.getToken());
        mFirebaseAuth.signInWithCredential(credential)
                .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
                    @Override
                    public void onComplete(@NonNull Task<AuthResult> task) {
                        Log.d(TAG, "signInWithCredential:onComplete:" + task.isSuccessful());

                        // If sign in fails, display a message to the user. If sign in succeeds
                        // the auth state listener will be notified and logic to handle the
                        // signed in user can be handled in the listener.
                        if (!task.isSuccessful()) {
                            Log.w(TAG, "signInWithCredential", task.getException());
                            Toast.makeText(SignInActivity.this, "Authentication failed.",
                                    Toast.LENGTH_SHORT).show();
                        }

                        else {
                            startActivity(new Intent(SignInActivity.this, MainActivity.class));
                            finish();
                        }
                    }
                });
    }

    /*
    private void handleFirebaseAuthResult(AuthResult authResult) {
        if (authResult != null) {
            // Welcome the user
            FirebaseUser user = authResult.getUser();
            Toast.makeText(this, "Welcome " + user.getEmail(), Toast.LENGTH_SHORT).show();

            // Go back to the main activity
            startActivity(new Intent(this, MainActivity.class));
        }
    }
    */

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.google_sign_in_button:
                signIn();
                break;
            default:
                return;
        }
    }

    private void signIn() {
        Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient);
        startActivityForResult(signInIntent, RC_SIGN_IN);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        mCallbackManager.onActivityResult(requestCode, resultCode, data);

        // Result returned from launching the Intent from GoogleSignInApi.getSignInIntent(...);
        if (requestCode == RC_SIGN_IN) {
            GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
            if (result.isSuccess()) {
                // Google Sign In was successful, authenticate with Firebase
                GoogleSignInAccount account = result.getSignInAccount();
                firebaseAuthWithGoogle(account);
            } else {
                // Google Sign In failed
                Log.e(TAG, "Google Sign In failed.");
            }
        }
    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
        // An unresolvable error has occurred and Google APIs (including Sign-In) will not
        // be available.
        Log.d(TAG, "onConnectionFailed:" + connectionResult);
        Toast.makeText(this, "Google Play Services error.", Toast.LENGTH_SHORT).show();
    }
}

Android Solutions


Solution 1 - Android

Go to Authentication > Sign-in providers, click Multiple accounts per email address and Allow creation of multiple accounts with the same email address is what you are looking for.

Account email address setting

Solution 2 - Android

Please check the thread: https://groups.google.com/forum/#!searchin/firebase-talk/liu/firebase-talk/ms_NVQem_Cw/8g7BFk1IAAAJ It explains why this happens. This is due to some security issue with Google emails being verified whereas Facebook emails are not.

Solution 3 - Android

I finally ended with this logic:

If user try to sign in with Facebook, but user with given email already exist (with Google provider) and this errors occures:

> "An account already exists with the same email address but different > sign-in credentials. Sign in using a provider associated with this > email address."

So, just ask user to loging using Google (and after it silently link Facebook to existing account)

Facebook and Google Sign In logics using firebase

Solution 4 - Android

> To minimize the login UI clicks without compromising the account security, Firebase Authentication has a concept of 'trusted provider', where the identity provider is also the email service provider. For example, Google is the trusted provider for @gmail.com addresses, Yahoo is the trusted provider for @yahoo.com addresses, and Microsoft for @outlook.com addresses. > In the "One Account per Email address" mode, Firebase Authentication tries to link account based on email address. If a user logins from trusted provider, the user immediately signs into the account since we know the user owns the email address. > If there is an existing account with the same email address but created with non-trusted credentials (e.g. non-trusted provider or password), the previous credentials are removed for security reason. A phisher (who is not the email address owner) might create the initial account - removing the initial credential would prevent the phisher from accessing the account afterwards.

Jin Liu

Solution 5 - Android

In firebase, it is very important to verify user email account the first time they login with Facebook , by sending a verification email.

Once email is verified, you can login with both Facebook and Gmail if user is using @gmail.com as email address.

> Facebook Login -> Click Link in Verification Email -> Gmail Login -> Facebook Login (OK)

> Facebook Login -> Gmail Login -> Click Link in Verification Email -> Facebook Login (NOT OK)

If you did not verify the Facebook email before user logout and try to login with their gmail, you will not be able to login with Facebook again the moment they login with their gmail.

Update - If you choose to always trust Facebook emails.

You can set up a firebase function (trigger) that automatically set emailVerified to true when the first login is via a facebook account.

Sample code.

const functions = require('firebase-functions');
const admin = require('firebase-admin');

exports.app = functions.auth.user().onCreate( async (user) => {

  if (user.providerData.find(d => d && d.providerId === 'facebook.com') || user.providerData === 'facebook.com') {
    
      try {
      await admin.auth().updateUser(user.uid, {
          emailVerified: true
        })
      } catch (err) {
        console.log('err when verifying email', err)
      }
  }
})

Doc: Firebase Auth Trigger

Solution 6 - Android

As Someone Special wrote, Facebook users need email verification as well as email and password (provider = password).

I suppose if a googe email is used by a user it automatically gets verified status.

The problem that should be fixed by Google:

If the user login by Facebook and then Gmail, the account is written if he has not completed email verification. Later email verification does not change anything, the account is overwritten

If user login by Facebook and verification emal (gmail),then every think is ok. can login using both social media providers

Solution 7 - Android

Allow creation of multiple accounts with the same email address is what you are looking for.

That works fine ONLY if you check the email in your backend and that's the reference for your users. If you use the Firebase Id then that won't enable to keep unique users.

Solution 8 - Android

I had the same issue. So I delete google account and facebook account in

> firebase console > authenticate > users

that you used to logged-in in before testing one anothers.

Solution 9 - Android

I had the same problem, all you have to do is go to Firebase Console and then in the "Authentication" category delete the user that you want.

That works to me.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDorukhan ArslanView Question on Stackoverflow
Solution 1 - AndroidfluxaView Answer on Stackoverflow
Solution 2 - AndroidbojeilView Answer on Stackoverflow
Solution 3 - AndroidpupadupaView Answer on Stackoverflow
Solution 4 - AndroidAlexis GamarraView Answer on Stackoverflow
Solution 5 - AndroidSomeone SpecialView Answer on Stackoverflow
Solution 6 - AndroidHelloGelloView Answer on Stackoverflow
Solution 7 - AndroidNelson La RoccaView Answer on Stackoverflow
Solution 8 - Androidohm89View Answer on Stackoverflow
Solution 9 - AndroidEli ElezraView Answer on Stackoverflow