2

I want to give user multiple options for signin. For now Google and Facebook. But when user signs in with Google and later tries to sign in with Facebook, I get an error:

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 I have to link the accounts. I didn't find any relevant documentation, except for this which didn't help much. The best info I could find is here on stackoverflow but for some reason it wasn't enough for me. The solution deals with signInWithPopup which gives me this error:

"Unable to establish a connection with the popup. It may have been blocked by the browser." So if the popup doesn't work, I call signInWithRedirect:

private authLoginWithPopup(provider: AuthProvider) {
  this.afAuth.auth.signInWithPopup(provider)
    .then((result) => {
      this.credential = result.credential;
      this.updateUserData(result);
    })
    .catch(error => {
      if (error.code === 'auth/account-exists-with-different-credential') {
        this.mergeCredentials(error);
      } else if (error.code === 'auth/popup-blocked') {
        this.authLoginWithRedirect(new firebase.auth.GoogleAuthProvider());
      }
    });
}

After Popup, you would go into then and could do whatever. But after redirect you lose your information. So I stored it in localStorage:

this.afAuth.auth.fetchProvidersForEmail(error.email).then(providers => {
  const [ provider ] = providers;
  if (provider === 'google.com') {
    const googleProvider = new firebase.auth.GoogleAuthProvider();
    googleProvider.setCustomParameters({ login_hint: error.email });
    localStorage.setItem('auth_error', JSON.stringify(error.credential));
    this.authLoginWithPopup(googleProvider);
  }
});

And I retrieve it in getRedirectResult:

constructor(
  private afAuth: AngularFireAuth,
  private afs: AngularFirestore,
) {
  this.afAuth.auth.getRedirectResult().then(result => {
    if (result.user) {
      this.credential = result.credential;
      const authError = JSON.parse(localStorage.getItem('auth_error'));

      if (authError) {
        localStorage.removeItem('auth_error');

        this.afAuth.auth.signInWithCredential(
          firebase.auth.GoogleAuthProvider.credential(
            null, (<any>this.credential).accessToken
          )
        )
          .then(user => {
            user.linkWithCredential(authError)
          })
          .catch(error => {
            console.log('error', error);
          });
      }
    }
  });
}

(This creates another problem. How can I know if it was a normal signInWithRedirect by a user who signed in with Google directly. Or if it was a redirect after user tried to sign in with Facebook and was prompted to sign in with Google because of this error? I don't want to merge credentials every time. But this is smaller problem. But I would love to here solution to this problem also.)

But the signInWithCredential gives me this error:

linkWithCredential failed: First argument "credential" must be a valid credential.

For which I've found "solution" here:

When calling signInWithCredential with a access token you need to call it like this:

signInWithCredential(null, token); If you are doing signInWithCredential(token) then you need to use an ID Token rather than an access token.

I tried both versions, both unsuccessfully. The second version: this.afAuth.auth.signInWithCredential(firebase.auth.GoogleAuthProvider.credential((<any>this.credential).idToken)) Type casting is there because there is problem in type definitions I guess.

I tried insert whole credentials object, the idToken as a first parameter, accessToken as a second parameter. But I always get: First argument "credential" must be a valid credential.

As I was writing this it crossed my mind that it probably is problem with linkWithCredential and not the signInWithCredential method. Which should be obvious based on the error message. I just didn't notice it because google search for the error gave me that solution. So I quickly checked what is in the authError variable. There is accessToken, providerId and signInMethod method. There is no tokenId. So I tried to send accessToken as a parameter, instead of the whole object, and it didn't help. I tried to send it as null, accessToken. Didn't help. I tried to find documentation for linkWithCredential method, without any luck. So I don't know what it is expecting.

I already wasted four days on this. It cost me much of my mental health. Can someone help me please?

Laker
  • 1,622
  • 4
  • 20
  • 32

1 Answers1

1

You can store the underlying OAuth credential (id token or access token): error.credential.idToken or error.credential.accessToken and its provider ID, error.credential.providerId. On return to page, you can re-initialize the credential, something like: firebase.auth.FacebookAuthProvider.credential(accessToken). After successful redirect sign in with Google, link that credential

bojeil
  • 29,642
  • 4
  • 69
  • 76