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
signInWithCredentialwith a access token you need to call it like this:
signInWithCredential(null, token);If you are doingsignInWithCredential(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?