Search code examples
firebasefacebookfirebase-authentication

missing emails in firebase auth for 20% of facebook credentials


I allow users to login with facebook on my app, backed by firebase authentication.

In around 20% of the facebook logins, I don't receive the user's email. I need the email address in my app, and can't figure out why I don't receive it.

Since I get the email address 80% of the time, I assume I have the right permissions setup to retrieve it.

I also enforced "One account per email address" in firebase-auth, so it seems to be a different issue than that raised in Firebase Auth missing email address.

Relevant extracts of my code:

export const FacebookSignUp: React.FC<SocialAuthProps & { title?: string }> = ({ onError, onSetWaiting, title }) => {
  async function onFacebookButtonPress() {
    onSetWaiting(true);
    const { email, first_name, accessToken } = await getFacebookUserData();
    const couldLogin = await tryLoginWithFacebook(email, accessToken);
    if (!couldLogin) {
      // Create a Firebase credential with the AccessToken
      const facebookCredential = FacebookAuthProvider.credential(accessToken);
      const userCredential = await firebaseAuth.signInWithCredential(facebookCredential);
      if (userCredential.user === null) {
        throw new Error("Null user");
      }
      const signupUser: SignupUserData = {
        userId: userCredential.user.uid,
        email,
        pseudo: first_name || undefined
      };
      await createSignupUser(signupUser).then(() => {
        onSetWaiting(false);
      });
    }
  }

  return (
    <SocialButton
      iconName="facebookIcon"
      title={title || "S'inscrire avec Facebook"}
      onPress={() =>
        onFacebookButtonPress().catch((err) => {
          onSetWaiting(false);
          if (err instanceof SocialAuthError) {
            onError(err);
          } else if (err instanceof Error) {
            const { message, name, stack } = err;
            serverError("Unexpected signup error", { message, name, stack });
          }
        })
      }
    />
  );
};

import { LoginManager, AccessToken, GraphRequest, GraphRequestManager } from "react-native-fbsdk";

export async function getFacebookUserData(): Promise<FacebookInfo> {
  LoginManager.logOut();
  const result = await LoginManager.logInWithPermissions(["public_profile", "email"]);

  if (result.isCancelled) {
    throw "User cancelled the login process";
  }

  // Once signed in, get the users AccesToken
  const { accessToken } = (await AccessToken.getCurrentAccessToken()) || {};
  if (!accessToken) {
    throw "Something went wrong obtaining access token";
  }

  return new Promise((resolve, reject) => {
    let req = new GraphRequest(
      "/me",
      {
        httpMethod: "GET",
        version: "v2.5",
        parameters: {
          fields: {
            string: "email,first_name"
          }
        }
      },
      (err, res) => {
        if (err || res === undefined) {
          reject(err);
        } else {
          const { first_name, email } = res as { first_name: string; email: string };
          resolve({ first_name, email, accessToken });
        }
      }
    );
    new GraphRequestManager().addRequest(req).start();
  });
}

Solution

  • Facebook allows you to opt out of passing your email along to third-party apps. You can request it, but the user can deny it.

    If I ever log in with Facebook I always opt out of passing my email along - most of the time, the third-party app doesn't need it for legitimate purposes.

    "I need the email address in my app" - why? email marketing? account duplication prevention?

    In cases where you did not get an email, assume the user has opted-out and/or doesn't have an email tied to their account. If you need one, ask the user to input a contact email address and explain what you are using it for. Expect some users to still opt out and plan around it.

    You could always convert their username into a non-existent email like [email protected] depending on your use case.