Link Firebase user with another provider

Viewed 171

I would like to force users that previously authenticated with Facebook to sign up using a new provider. The reason for this is that I would like to remove Facebook as an authentication provider. I would unlink the user once the user has been successfully linked with the new provider.

For example, the user is presented with new authentication options and the user selects to continue with email. I have the following code:

func createUserAndSignIn(
    username: String,
    email: String,
    password: String
) async throws -> String {
    let credential = EmailAuthProvider.credential(withEmail: email, password: password)

    // if user is already logged in (in this case with Facebook)
    if let user = Auth.auth().currentUser {
        try await user.link(with: credential)
    }

    do {
        let authDataResult = try await Auth.auth().createUser(withEmail: email, password: password)
        return authDataResult.user.uid
    } catch {
        // throw error
    }
}

The linking of accounts (user.link(with:)) fails with the following error:

Domain=FIRAuthErrorDomain Code=17014 "This operation is sensitive and requires recent authentication. Log in again before retrying this request." UserInfo={NSLocalizedDescription=This operation is sensitive and requires recent authentication. Log in again before retrying this request., FIRAuthErrorUserInfoNameKey=ERROR_REQUIRES_RECENT_LOGIN}

Would this be even be the correct approach for this?

2 Answers

You have to re-authenticate the user. Using the current credential

if 
    let user = Auth.auth().currentUser,
    let currentAccessToken = AccessToken.current 
{
    // Prompt the user to re-provide their sign-in credentials
    let result = try await user.reauthenticate(
        with: FacebookAuthProvider.credential(withAccessToken: currentAccessToken.tokenString)
    )

    // Then link the user
    try await user.link(with: newCredential)
    // User can be unlinked from Facebook
    try await Auth.auth().currentUser?.unlink(fromProvider: "facebook.com")
}

This is needed for several operations such as updating the user's email, password or deleting the user.

The approach you're taking is close. The error you're getting is because some operations in firebase require a recent authentication to have taken place:

FIRAuthErrorCodeRequiresRecentLogin: Updating a user’s email is a security sensitive operation that requires a recent login from the user. This error indicates the user has not signed in recently enough. To resolve, reauthenticate the user by invoking reauthenticateWithCredential:completion: on FIRUser. [1]

The steps you want to take are:

  1. Authenticate the user with an existing auth method.
  2. Prompt the user for their email and password
  3. Use the email and password to create an AuthCredential object.
  4. Pass that AuthCredential object to the user's linkWithCredential method.

There's a complete walkthrough for this in the Firebase docs: https://firebase.google.com/docs/auth/web/account-linking#link-email-address-and-password-credentials-to-a-user-account

But the key point is that you have to authenticate the user with an existing provider before you do this, even if they are technically "logged in".

Note that the steps are slightly different if you want to link the user to another Auth provider other than email (such as Google): https://firebase.google.com/docs/auth/web/account-linking#link-federated-auth-provider-credentials-to-a-user-account

After that, if you wish, you can use unlink to remove the Facebook authentication.

Related