在 Firebase 中处理关联帐户

Handling linking accounts in Firebase

我正在按照 Firebase 关于社交登录的说明进行操作。下面是我正在使用的示例,从登录身份验证的角度来看,它一切正常。

但是,我 Google 和 Facebook 登录都是独立工作的。

我现在希望能够做的是 link 帐户。实际上,您可以在下面看到这可能发生的情况(请参阅评论):

If you are using multiple auth providers on your app you should handle linking the user's accounts here.

我已经尝试了很多我认为应该放在这里的变体,但都无济于事。任何人都可以就他们认为应该放在这里的内容指导我吗?谢谢!

function initFBApp() {
    // Result from Redirect auth flow.
    // [START getidptoken]
    firebase.auth().getRedirectResult().then(function (result) {
        if (result.credential) {
            // This gives you a Facebook Access Token. You can use it to access the Facebook API.
            var token = result.credential.accessToken;
            // [START_EXCLUDE]
            document.getElementById('FBquickstart-oauthtoken').textContent = token;
        }
        else {
            document.getElementById('FBquickstart-oauthtoken').textContent = 'null';
            // [END_EXCLUDE]
        }
        // The signed-in user info.
        var user = result.user;
    }).catch(function (error) {
        // Handle Errors here.
        var errorCode = error.code;
        var errorMessage = error.message;
        // The email of the user's account used.
        var email = error.email;
        // The firebase.auth.AuthCredential type that was used.
        var credential = error.credential;
        // [START_EXCLUDE]
        if (errorCode === 'auth/account-exists-with-different-credential') {
            alert('You have already signed up with a different auth provider for that emails.');
            // If you are using multiple auth providers on your app you should handle linking
            // the user's accounts here.
        }
        else {
            console.error(error);
        }
        // [END_EXCLUDE]
    });
    // [END getidptoken]
    // Listening for auth state changes.
    // [START authstatelistener]
    firebase.auth().onAuthStateChanged(function (user) {
        if (user) {
            // User is signed in.
            var displayName = user.displayName;
            var email = user.email;
            var emailVerified = user.emailVerified;
            var photoURL = user.photoURL;
            var isAnonymous = user.isAnonymous;
            var uid = user.uid;
            var providerData = user.providerData;
            // [START_EXCLUDE]
            document.getElementById('FBquickstart-sign-in-status').textContent = 'Signed in';
            document.getElementById('FBquickstart-sign-in').textContent = 'Log out';
            document.getElementById('FBquickstart-account-details').textContent = JSON.stringify(user, null, '  ');
            // [END_EXCLUDE]
        }
        else {
            // User is signed out.
            // [START_EXCLUDE]
            document.getElementById('FBquickstart-sign-in-status').textContent = 'Signed out';
            document.getElementById('FBquickstart-sign-in').textContent = 'Log in with Facebook';
            document.getElementById('FBquickstart-account-details').textContent = 'null';
            document.getElementById('FBquickstart-oauthtoken').textContent = 'null';
            // [END_EXCLUDE]
        }
        // [START_EXCLUDE]
        document.getElementById('FBquickstart-sign-in').disabled = false;
        // [END_EXCLUDE]
    });
    // [END authstatelistener]
    document.getElementById('FBquickstart-sign-in').addEventListener('click', toggleFBSignIn, false);
}

大致的处理步骤如下auth/account-exists-with-different-credential: 如果您登录的新 Facebook 帐户使用的是另一个已存在帐户的电子邮件地址,则会出现该错误。假设现有帐户是 google 帐户。

您将在 getRedirectResult().catch(function(error) {})

中收到该错误

该错误还将包含电子邮件和凭据字段。 您将需要保存凭据(使用推荐的 sessionStorage)。查看此 post 了解更多信息:

然后您调用 firebase.auth().fetchProvidersForEmail(error.email) 以确定该电子邮件已经存在的提供商。

然后您将登录这些现有提供商之一并断言该电子邮件与 error.email 相同。成功后,您将从 sessionStorage 加载挂起的凭据,按照其他 post 和 link 中的描述重新初始化到当前用户:

firebase.auth().currentUser.linkWithCredential(savedCred);

您现在将拥有两个帐户 linked。请记住,现有提供程序可能是密码类型。在这种情况下,您不需要保存凭据,只需询问用户密码并使用相同的电子邮件 error.email 登录即可。然后,您可以使用 error.credential.

直接调用 link

顺便说一句,我推荐 firebaseui-web,它会为您处理所有这一切: https://github.com/firebase/firebaseui-web

我认为 Firebase API changed a bitfirebase.auth().currentUser.link(savedCred); 现在是 firebase.auth().currentUser.linkWithRedirect(provider)。在我的实现中,我将最初选择的提供程序保存到 sessionStorage,并在需要帐户链接时将其与上述方法一起使用。

如果更适合您的需要,您也可以执行 linkWithPopUp

下面是相关的工作代码片段(位于异步函数内)。请注意,"apples" 只是 Firestore 中代表购物车的简化测试记录。

if(error.code === "auth/email-already-in-use"){
    // REMEMBER AUTH CURRENT USER OBJECT
    previousUser = firebase.auth().currentUser;
    // WE MUST HANDLE DB READ AND DELETE WHILE SIGNED IN AS PREVIOUS USER PER FIRESTORE SECURITY RULES
    if(localUserDoc){ //this was saved from .snapshot firing
        if(localUserDoc.data().apples){
            apples = localUserDoc.data().apples;
        }                    
    }
    //DELETE CURRENT USER RECORD WHILE STILL SIGNED IN
    await firebase.firestore().collection("users").doc(previousUser.uid).delete();
    // CLEAN UP DONE. NOW SIGN IN USING EMAIL LINK CREDENTIAL
    try {
        var firebaseUserObj = await firebase.auth().signInAndRetrieveDataWithCredential(credential);
        // FIRESTORE USER RECORD FOR EMAIL LINK USER WAS CREATED WHEN THEY ADDED APPLE TO CART
        try {
            var doc = await firebase.firestore().collection("users").doc(firebaseUserObj.user.uid).get();
            if (doc.exists) {
                if(doc.data().apples){
                    apples = apples + doc.data().apples;
                }
            }
            await firebase.firestore().collection("users").doc(firebaseUserObj.user.uid).update({
                apples: apples
            });
        } catch(error) {
            console.log("Error getting document:", error);
        }
        previousUser.delete();
    } catch (error) {
        console.log(".signInWithCredential err ", error);
    }
}

仔细阅读示例https://firebase.google.com/docs/auth/web/google-signin 部分 "Handling account-exists-with-different-credential Errors"

Redirect mode This error is handled in a similar way in the redirect mode, with the difference that the pending credential has to be cached between page redirects (for example, using session storage).