Dan F.
Dan F.

Reputation: 61

Check if Firebase Facebook user exists without creating a user starting from anonymous user

In Firebase I need to check if a Facebook user exists without creating the user. Initially the user is anonymous, and they try to login with Facebook. I want this to fail if the Facebook account is not already linked to a user in my system. It won't be linked to the current user because they are anonymous,

If I use Auth.signInAndRetrieveDataWithCredential I expected a "auth/user-not-found" error, but instead the user is simply created. Is this a bug or expected?

let credential = firebase.auth.FacebookAuthProvider.credential(
        event.authResponse.accessToken)
    firebase.auth().signInAndRetrieveDataWithCredential(credential).then( (userCredential) => {
        let user = userCredential.user
        app.debug("DEBUG: Existing user signed in:"+user.uid)
        this.loginSuccess(user)
    }).catch( (err) => {
        app.error("ERROR re-signing in:"+err.code)
        $("#login_status_msg").text(err)
    })

If I use User.reauthenticateAndRetrieveDataWithCredential instead I get the error "auth/user-mismatch" which makes sense because user is currently anonymous. However, I was expecting "auth/user-not-found" may be thrown instead if the credential doesn't exist, but that doesn't happen.

I don't see a way to take my anonymous user, have them login with Facebook and then see if another user is already linked to that Facebook credential without creating the user if it doesn't exist.

If you're wondering why? My scenario is: The system allows anonymous users

  1. A user logs in, then converts to a logged in user by registering with Facebook.
  2. App uninstall
  3. App reinstall
  4. User starts up the app and is initially anonymous.
  5. They try and login with Facebook again. At this point I want to stop them from creating a user if they don't have one already. If they have a user ID already, the code works fine and changes their anonymous account ID to the original user ID which is good.

Upvotes: 4

Views: 2295

Answers (4)

Derek Dawson
Derek Dawson

Reputation: 522

What I did to solve this problem without relying on the call to linkAndRetrieveDataWithCredential to fail and using the catch block to sign in the already existing user is to save the userID field that getCurrentAccessToken returns.

const { userID } = data;
this.props.setFacebookId(userID); // saves the userID on the server

I can later check if this userID already exists next time the user signs up with facebook.

Upvotes: 0

Noah Allen
Noah Allen

Reputation: 796

I found a solution! It wasn't too hard to implement, but it does seem hacky.

So we know that when using signInAndRetrieveDataWithCredential(cred) for facebook login, the account is created even if it does not exist yet. To solve this, we need to make sure that we handle the following three things:

  1. Detect if the account is new
  2. Delete the current account that was created by firebase
  3. Throw an error to get out of the current flow and return to wherever you were before.

I just implemented and tested this solution, and it seems to work great:

// ... do your stuff to do fb login, get credential, etc:
const userInfo = await firebase.auth().signInAndRetrieveDataWithCredential(credential)

// userInfo includes a property to check if the account is new:
const isNewUser = _.get(userInfo, 'additionalUserInfo.isNewUser', true)

// FIRST, delete the account we just made.
// SECOND, throw an error (or otherwise escape the current context.
if (isNewUser) {
  firebase.auth().currentUser.delete()
  throw new Error('Couldn\'t find an existing account.')
}

// If the user already exists, just handle normal login
return userInfo.user

The reason I did this was to ensure that users had to go through the "create account flow" in my app. Your case would be really easy to implement as well, something like the following:

let credential = firebase.auth.FacebookAuthProvider.credential(event.authResponse.accessToken)

firebase.auth().signInAndRetrieveDataWithCredential(credential)
  .then(userCredential => {
    const isNewUser = userCredential.additionalUserInfo.isNewUser
    if (isNewUser) {
      firebase.auth().currentUser.delete()
      // The following error will be handled in your catch statement
      throw new Error("Couldn't find an existing account.")
    }
    // Otherwise, handle login normally:
    const user = userCredential.user
    app.debug("DEBUG: Existing user signed in:"+user.uid)
    this.loginSuccess(user)
  }).catch( (err) => {
    app.error("ERROR re-signing in:"+err.code)
    $("#login_status_msg").text(err)
  })

Upvotes: 4

Francisco Durdin Garcia
Francisco Durdin Garcia

Reputation: 13357

You can use the method fetchSignInMethodsForEmail to check if an specific email is already associated to an specific provider or not. Doing this you will be able to check if one if the SighInMethods of the email associated to your user contains Facebook.com or not.

I show you below an example about how I manage this cases on my application. I'm using an RxJavaWrapper on my code, but you will understand the point of how to manage it:

 RxFirebaseAuth.fetchSignInMethodsForEmail(authInstance, email)
                .flatMap { providerResult ->
                    if (!providerResult.signInMethods!!.contains(credential.provider)) {
                        return@flatMap Maybe.error<AuthResult>(ProviderNotLinkedException(credential.provider))
                    } else {
                        return@flatMap RxFirebaseAuth.signInWithCredential(authInstance, credential)
                    }
                }
                .subscribe({ authResult ->
                  //Manage success
                }, { error ->
                  //Manage error
                })
  • First I check the providers associated to the email of the user(You can retrieve it from the provider)
  • If the list of SignInMethods contains my credential provider, I throw an error, if not, I call my signInWithCredential method to create the user.
  • Continue your workflow.

Upvotes: 0

bojeil
bojeil

Reputation: 30848

You can use linkAndRetrieveDataWithCredential:

let credential = firebase.auth.FacebookAuthProvider.credential(
    event.authResponse.accessToken);
anonymousUser.linkAndRetrieveDataWithCredential(credential).then( (userCredential) => {
  // Firebase Auth only allows linking a credential if it is not
  // already linked to another account.
  // Now the anonymous account is upgraded to a permanent Facebook account.
}).catch( (err) => {
  // Check for code: auth/credential-already-in-use
  // When this error is returned, it means the credential is already
  // used by another account. 
})

Upvotes: 0

Related Questions