我们在尝试解决此问题时遇到了实际问题,因此希望获得一些Firebase帮助/解决了同样问题的帮助。
该应用程序是React Native(0.43.2)并使用Firebase JS API(最新版)
我们提供Facebook和Google身份验证。工作正常。
但是,如果是用户:
auth/account-exists-with-different-credential
通过阅读文档和SO上的一些帖子,我们认为以下内容是正确的,但显然不是因为我们正在获得相同的身份验证错误。
...error returned by Firebase auth after trying Facebook login...
const email = error.email;
const pendingCred = error.credential;
firebase.auth().fetchProvidersForEmail(email)
.then(providers => {
//providers returns this array -> ["google.com"]
firebase.auth().signInWithCredential(pendingCred)
.then(result => {
result.user.link(pendingCred)
})
.catch(error => log(error))
对signInWithCredential的调用抛出相同的错误auth/account-exists-with-different-credential
。
任何人都可以帮助指出我们在这个实现中做错了什么吗?非常感谢。
答案 0 :(得分:12)
Firebase为所有电子邮件强制执行相同的帐户。由于您已经拥有同一封电子邮件的Google帐户,因此您需要将该Facebook帐户与Google帐户相关联,以便用户可以访问相同的数据,并且下次可以使用Google或Facebook登录同一帐户。< / p>
您的代码段中的问题是您使用相同的凭据进行签名和链接。修改如下。 当您收到错误&#39; auth / account-exists-with-different-credential&#39;时, 该错误将包含error.email和error.credential(Facebook OAuth凭证)。您需要先查找error.email以获取现有提供程序。
firebase.auth().fetchProvidersForEmail(error.email)
.then(providers => {
//providers returns this array -> ["google.com"]
// You need to sign in the user to that google account
// with the same email.
// In a browser you can call:
// var provider = new firebase.auth.GoogleAuthProvider();
// provider.setCustomParameters({login_hint: error.email});
// firebase.auth().signInWithPopup(provider)
// If you have your own mechanism to get that token, you get it
// for that Google email user and sign in
firebase.auth().signInWithCredential(googleCred)
.then(user => {
// You can now link the pending credential from the first
// error.
user.linkWithCredential(error.credential)
})
.catch(error => log(error))
答案 1 :(得分:2)
由于谷歌是@ gmail.com地址的可信赖提供商,因此它比使用Gmail作为电子邮件的其他帐户获得更高的优先级。这就是为什么如果您使用Facebook登录然后Gmail不会引发错误,但如果您尝试将Gmail转到Facebook,那么它会抛出一个。
请参阅this question。
如果您想允许多个帐户使用相同的电子邮件,请转到Firebase控制台并在身份验证下 - &gt;登录方法,底部应该有一个选项来切换它。
答案 2 :(得分:2)
我已经写了关于如何执行此操作而无需在这里第二次登录的信息:
https://blog.wedport.co.uk/2020/05/29/react-native-firebase-auth-with-linking/
在链接帐户之前,您需要存储原始凭证并以静默方式获取登录信息。链接中的完整代码:
signInOrLink: async function (provider, credential, email) {
this.saveCredential(provider, credential)
await auth().signInWithCredential(credential).catch(
async (error) => {
try {
if (error.code != "auth/account-exists-with-different-credential") {
throw error;
}
let methods = await auth().fetchSignInMethodsForEmail(email);
let oldCred = await this.getCredential(methods[0]);
let prevUser = await auth().signInWithCredential(oldCred);
auth().currentUser.linkWithCredential(credential);
}
catch (error) {
throw error;
}
}
);
}
答案 3 :(得分:1)
有时 firebase 文档很棒,有时它会让您想要更多。在这种情况下,在处理错误时,它给出了关于 signInWithPopup
的非常详细的说明。但是,signInWithRedirect
的完整说明是...
重定向模式
此错误在重定向模式中的处理方式类似,不同之处在于必须在页面重定向之间缓存挂起的凭据(例如,使用会话存储)。
根据@bojeil 和@Dominic 的回答,您可以通过以下方式将 Facebook 帐户与调用 signInWithRedirect
的 Google 帐户关联起来。
const providers = {
google: new firebase.auth.GoogleAuthProvider(),
facebook: new firebase.auth.FacebookAuthProvider(),
twitter: new firebase.auth.TwitterAuthProvider(),
};
const handleAuthError = async (error) => {
if (error.email && error.credential && error.code === 'auth/account-exists-with-different-credential') {
// We need to retain access to the credential stored in `error.credential`
// The docs suggest we use session storage, so we'll do that.
sessionStorage.setItem('credential', JSON.stringify(error.credential));
const signInMethods = await firebase.auth().fetchSignInMethodsForEmail(error.email); // -> ['google.com']
const providerKey = signInMethods[0].split('.')[0]; // -> 'google'
const provider = providers[providerKey]; // -> providers.google
firebase.auth().signInWithRedirect(provider);
}
};
const handleRedirect = async () => {
try {
const result = await firebase.auth().getRedirectResult();
const savedCredential = sessionStorage.getItem('credential');
// we found a saved credential in session storage
if (result.user && savedCredential) {
handleLinkAccounts(result.user, savedCredential);
}
return result;
}
catch (error) {
handleAuthError(error);
}
};
const handleLinkAccounts = (authUser, savedCredential) => {
// Firebase has this little hidden gem of a method call fromJSON
// You can use this method to parse the credential saved in session storage
const token = firebase.auth.AuthCredential.fromJSON(savedCredential);
const credential = firebase.auth.FacebookAuthProvider.credential(token);
authUser.linkWithCredential(credential);
// don't forget to remove the credential
sessionStorage.removeItem('credential');
};
firebase.auth().onAuthStateChanged((authUser) => {
handleRedirect();
});
答案 4 :(得分:0)
我发现Firebase选择此行为作为默认行为感到奇怪和不便,并且解决方案也不是简单的。在撰写本文时,这是基于@bojeil的答案的Firebase完整和更新的解决方案。
function getProvider(providerId) {
switch (providerId) {
case firebase.auth.GoogleAuthProvider.PROVIDER_ID:
return new firebase.auth.GoogleAuthProvider();
case firebase.auth.FacebookAuthProvider.PROVIDER_ID:
return new firebase.auth.FacebookAuthProvider();
case firebase.auth.GithubAuthProvider.PROVIDER_ID:
return new firebase.auth.GithubAuthProvider();
default:
throw new Error(`No provider implemented for ${providerId}`);
}
}
const supportedPopupSignInMethods = [
firebase.auth.GoogleAuthProvider.PROVIDER_ID,
firebase.auth.FacebookAuthProvider.PROVIDER_ID,
firebase.auth.GithubAuthProvider.PROVIDER_ID,
];
async function oauthLogin(provider) {
try {
await firebase.auth().signInWithPopup(provider);
} catch (err) {
if (err.email && err.credential && err.code === 'auth/account-exists-with-different-credential') {
const providers = await firebase.auth().fetchSignInMethodsForEmail(err.email)
const firstPopupProviderMethod = providers.find(p => supportedPopupSignInMethods.includes(p));
// Test: Could this happen with email link then trying social provider?
if (!firstPopupProviderMethod) {
throw new Error(`Your account is linked to a provider that isn't supported.`);
}
const linkedProvider = getProvider(firstPopupProviderMethod);
linkedProvider.setCustomParameters({ login_hint: err.email });
const result = await firebase.auth().signInWithPopup(linkedProvider);
result.user.linkWithCredential(err.credential);
}
// Handle errors...
// toast.error(err.message || err.toString());
}
}
答案 5 :(得分:0)
我通过电子邮件发送了Firebase支持,他们向我解释了更多。用自己的话说:
为提供上下文,不同的电子邮件具有自己的身份提供者。如果用户的电子邮件为sample@gmail.com,则该电子邮件的IDP(身份提供商)将为Google(由域@ gmail.com指定)(对于域为@的电子邮件,则为true) mycompany.com或@ yahoo.com)。
Firebase身份验证允许在检测到使用的提供商是电子邮件的IDP时进行登录,而不管他们是否正在使用设置“每个电子邮件地址一个帐户”并且是否已使用以前的提供商登录,例如基于电子邮件/密码的身份验证或任何联合身份提供者,例如Facebook。这意味着,如果他们使用电子邮件和密码登录sample@gmail.com,则Google(在“每个电子邮件地址一个帐户”设置下)将由Firebase允许,并且帐户的提供商将更新为Google。原因是IDP很可能会获得有关电子邮件的最新信息。
另一方面,如果他们首先使用Google登录,然后使用具有相同关联电子邮件的电子邮件和密码帐户登录,则我们不想更新其IDP,并且将继续使用默认行为通知用户已经是与该电子邮件关联的帐户。