Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

signInWithPhoneNumber - INVALID_APP_CREDENTIAL sometimes #8094

Open
2 of 10 tasks
PierreCapo opened this issue Oct 28, 2024 · 4 comments
Open
2 of 10 tasks

signInWithPhoneNumber - INVALID_APP_CREDENTIAL sometimes #8094

PierreCapo opened this issue Oct 28, 2024 · 4 comments
Labels

Comments

@PierreCapo
Copy link

Issue

Sometimes, when using the function signInWithPhoneNumber, Firebase Console reports the error: INVALID_APP_CREDENTIAL and our users can not sign in.

I am not sure yet if they receive the OTP code as I am unable to reproduce it at all on my phones.
This issue happens for around 5-10 users per day. I would say that's around 10% of our users that sign up. Other ones are fine.
We haven't had this issue on Android, but we have very very few users on Android, so that might be just pure luck.

image

Project Files

Javascript

import {AppText} from '@design/AppText';
import {colors} from '@design/colors';
import {LoginStackScreenProps} from '@misc/navigation';
import {Onboarding} from '@screens/Onboarding/components/Onboarding';
import React, {useEffect, useState} from 'react';
import {Keyboard, Pressable, StyleSheet, View} from 'react-native';
import {TextInput} from 'react-native-gesture-handler';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
import messaging from '@react-native-firebase/messaging';

import auth from '@react-native-firebase/auth';
import {AnalyticsClient} from '@clients/AnalyticsClient/AnalyticsClient';
import {useMutation} from 'convex/react';
import {api} from 'convex/_generated/api';
import {DEFAULT_USER_DATA} from '@misc/const';
import {AppToast} from '@design/AppToast';
import {FirebaseAuthError} from 'node_modules/firebase-admin/lib/utils/error';
import crashlytics from '@react-native-firebase/crashlytics';

export const OnboardingVerifyPhoneNumberScreen = (
  props: LoginStackScreenProps<'VerifyPhoneNumber'>,
) => {
  const insets = useSafeAreaInsets();
  const createUserIfDoesNotExist = useMutation(api.users.create);
  const [textInputValue, setTextInputValue] = useState('');
  const [status, setStatus] = useState<
    'timer' | 'resend' | 'processing' | 'finished' | 'error'
  >('timer');
  const [confirmation, setConfirmation] = useState(
    props.route.params.confirmation,
  );
  const [timer, setTimer] = useState(30);

  useEffect(() => {
    if (textInputValue.length === 6) {
      confirmCode();
    }
    async function confirmCode() {
      Keyboard.dismiss();
      setStatus('processing');
      try {
        const authUser = await confirmation.confirm(textInputValue);
        AnalyticsClient.trackEvent('Verified Phone Number');
        if (authUser != null) {
          const messagingToken = await messaging().getToken();
          await createUserIfDoesNotExist({
            authId: authUser.user.uid,
            messagingToken,
            ...DEFAULT_USER_DATA,
          });
        }
        setStatus('finished');
      } catch (_error) {
        const error = _error as FirebaseAuthError;
        crashlytics().log(error?.code);
        crashlytics().log(error?.message);
        crashlytics().recordError(new Error('AUTH_SMS_VERIFICATION_ISSUE'));
        switch (error?.code) {
          case 'auth/invalid-verification-code':
            AppToast.showError('Invalid code.');
            break;
          case 'auth/session_expired':
            AppToast.showError(
              'Too much time ocurred to use the code. Go back and try again.',
            );
            break;
          default:
            AppToast.showError('An error occurred.');
            break;
        }
        setStatus('error');
        console.log(error);
      }
    }
  }, [
    textInputValue,
    props.route.params.phoneNumber,
    confirmation,
    createUserIfDoesNotExist,
  ]);

  useEffect(() => {
    let interval: NodeJS.Timeout;

    if (timer > 0) {
      interval = setInterval(() => {
        setTimer(Math.max(timer - 1, 0));
      }, 1000);
    } else if (timer === 0) {
      setStatus(old => (old === 'timer' ? 'resend' : old));
    }

    return () => clearInterval(interval);
  }, [timer]);

  const handleResend = async () => {
    const result = await auth().signInWithPhoneNumber(
      props.route.params.phoneNumber,
    );
    setConfirmation(result);
    setTimer(60); // Reset timer
    setStatus('timer');
  };

  return (
    <View
      style={{
        flex: 1,
        gap: 8,
        paddingTop: insets.top + 36,
        paddingHorizontal: 24,
        backgroundColor: 'white',
      }}>
      <Onboarding.HeaderText
        title={'Enter your code'}
        subtitle={`A text message was sent to ${props.route.params.phoneNumber}`}
      />

      <View
        style={{
          flexDirection: 'row',
          gap: 8,
          alignSelf: 'center',
          marginTop: 32,
        }}>
        {[0, 1, 2, 3, 4, 5].map((el, index) => (
          <View
            key={el}
            style={{
              height: 45,
              width: 35,
              borderRadius: 4,
              borderWidth: 1,
              borderColor:
                textInputValue.length === index
                  ? '#229ED9DD'
                  : colors.grayscale.line,
              justifyContent: 'center',
              alignItems: 'center',
            }}>
            <AppText
              style={{
                fontSize: 18,
                fontWeight: '600',
                color: colors.grayscale.header,
              }}>
              {textInputValue[index]}
            </AppText>
          </View>
        ))}

        <TextInput
          autoFocus
          keyboardType="number-pad"
          caretHidden
          style={[StyleSheet.absoluteFillObject, {color: 'transparent'}]}
          placeholder="Enter code"
          maxLength={6}
          placeholderTextColor="transparent"
          onChangeText={setTextInputValue}
        />
      </View>

      <View style={{alignItems: 'center', marginTop: 32}}>
        {status === 'processing' ? (
          <AppText
            style={{
              fontWeight: '500',
              color: colors.grayscale.header,
            }}>
            Processing SMS code...
          </AppText>
        ) : status === 'resend' ? (
          <Pressable onPress={handleResend} hitSlop={10}>
            <AppText style={{color: colors.primary.default, fontWeight: '500'}}>
              Resend code
            </AppText>
          </Pressable>
        ) : status === 'timer' ? (
          <AppText style={{color: colors.grayscale.header}}>
            Please wait {timer} seconds to resend the code.
          </AppText>
        ) : status === 'error' ? (
          <View>
            <AppText style={{color: colors.grayscale.header}}>
              An error ocurred while processing the code...
            </AppText>
            <Pressable onPress={handleResend} hitSlop={10}>
              <AppText
                style={{color: colors.primary.default, fontWeight: '500'}}>
                Resend code
              </AppText>
            </Pressable>
          </View>
        ) : null}
      </View>
    </View>
  );
};

package.json:

{
  "name": "app",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "lint": "eslint .",
    "start": "react-native start",
    "test": "jest",
    "seed:dev": "npx convex import --replace sources/seed/seed.zip"
  },
  "dependencies": {
    "@invertase/react-native-apple-authentication": "^2.3.0",
    "@purchasely/react-native-purchasely-google": "4.5.3",
    "@react-native-async-storage/async-storage": "^1.24.0",
    "@react-native-firebase/app": "^21.0.0",
    "@react-native-firebase/auth": "^21.0.0",
    "@react-native-firebase/crashlytics": "^21.0.0",
    "@react-native-firebase/messaging": "^21.0.0",
    "@react-native-firebase/storage": "^21.0.0",
    "@react-native-google-signin/google-signin": "^11.0.1",
    "@react-native-menu/menu": "^1.1.2",
    "@react-navigation/bottom-tabs": "6.6.1",
    "@react-navigation/native": "^6.1.18",
    "@react-navigation/native-stack": "6.11.0",
    "@shopify/flash-list": "^1.7.1",
    "compare-versions": "^6.1.1",
    "convex": "^1.17.0",
    "convex-helpers": "^0.1.61",
    "firebase-admin": "^12.3.0",
    "mixpanel-react-native": "^3.0.5",
    "react": "18.3.1",
    "react-dom": "18.2.0",
    "react-native": "0.75.4",
  },
  "engines": {
    "node": ">=18"
  },
  "packageManager": "yarn@4.3.1",
}

iOS

Click To Expand

ios/Podfile:

  • I'm not using Pods
  • I'm using Pods and my Podfile looks like:
require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking")
# Resolve react_native_pods.rb with node to allow for hoisting
require Pod::Executable.execute_command('node', ['-p',
  'require.resolve(
    "react-native/scripts/react_native_pods.rb",
    {paths: [process.argv[1]]},
  )', __dir__]).strip

platform :ios, '14.0'
prepare_react_native_project!

linkage = ENV['USE_FRAMEWORKS']
if linkage != nil
  Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
  use_frameworks! :linkage => linkage.to_sym
end


target 'app' do
  use_expo_modules!
  post_integrate do |installer|
    begin
      expo_patch_react_imports!(installer)
    rescue => e
      Pod::UI.warn e
    end
  end
  config = use_native_modules!
  use_frameworks! :linkage => :static
  $RNFirebaseAsStaticFramework = true

  rn_maps_path = '../node_modules/react-native-maps'
  pod 'react-native-google-maps', :path => rn_maps_path

  use_react_native!(
    :path => config[:reactNativePath],
    # Enables Flipper.
    #
    # Note that if you have use_frameworks! enabled, Flipper will not work and
    # you should disable the next line.
   # :flipper_configuration => flipper_config,
    # An absolute path to your application root.
    :app_path => "#{Pod::Config.instance.installation_root}/.."
  )

  # --- PATCH FOR REANIMATED ---
  # See https://github.com/software-mansion/react-native-reanimated/issues/4425
  # and shared transitions. `use_frameworks! :linkage => :static` is used because of Firebase.
  $static_framework = ['RNScreens']

  pre_install do |installer|
    Pod::Installer::Xcode::TargetValidator.send(:define_method, :verify_no_static_framework_transitive_dependencies) {}
    installer.pod_targets.each do |pod|
      if $static_framework.include?(pod.name)
        def pod.build_type;
          Pod::BuildType.static_library
        end
      end
    end
  end
  # --- END PATCH ---


  post_install do |installer|
    # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202
    react_native_post_install(
      installer,
      config[:reactNativePath],
      :mac_catalyst_enabled => false,
      # :ccache_enabled => true
    )

    # -- PATCH: compile signing errors
    installer.generated_projects.each do |project|
      project.targets.each do |target|
          target.build_configurations.each do |config|
              config.build_settings["DEVELOPMENT_TEAM"] = "XXXXXXXXXX"
           end
      end
    end
    # --- END PATCH ---

  end
end

AppDelegate.m:

#import "AppDelegate.h"
#import <Firebase.h>
#import <React/RCTBundleURLProvider.h>
#import <GoogleMaps/GoogleMaps.h>
#import "RNCConfig.h"
#import <React/RCTLinkingManager.h>
#import <RiveRuntime/RenderContextManager.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  self.moduleName = @"main";
  // You can add your custom initial props in the dictionary below.
  // They will be passed down to the ViewController used by React Native.
  self.initialProps = @{};
  [FIRApp configure];
  [GMSServices provideAPIKey: [RNCConfig envFor:@"GOOGLE_MAPS_API_KEY"]]; // add this line using the api key obtained from Google Console
  
  // Used to fix Skia rendering performance.
  // This is very visible when the app is first run and Skia needed to warm up.
  // So we drop Skia and use the custom rive renderer
  [[RenderContextManager shared] setDefaultRenderer:riveRenderer];
  
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
  return [self bundleURL];
}

- (NSURL *)bundleURL
{
#if DEBUG
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}

// For universal links: https://reactnavigation.org/docs/deep-linking/#setup-on-ios
- (BOOL)application:(UIApplication *)application
   openURL:(NSURL *)url
   options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
  return [RCTLinkingManager application:application openURL:url options:options];
}

// For universal links: https://reactnavigation.org/docs/deep-linking/#setup-on-ios
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity
 restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
 return [RCTLinkingManager application:application
                  continueUserActivity:userActivity
                    restorationHandler:restorationHandler];
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    // Reset the badge number to 0
    application.applicationIconBadgeNumber = 0;
}

@end


Android

Click To Expand

Have you converted to AndroidX?

  • my application is an AndroidX application?
  • I am using android/gradle.settings jetifier=true for Android compatibility?
  • I am using the NPM package jetifier for react-native compatibility?

android/build.gradle:

// N/A

android/app/build.gradle:

// N/A

android/settings.gradle:

// N/A

MainApplication.java:

// N/A

AndroidManifest.xml:

<!-- N/A -->


Environment

Click To Expand

react-native info output:

System:
  OS: macOS 15.0.1
  CPU: (10) arm64 Apple M1 Pro
  Memory: 95.42 MB / 32.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 18.17.1
    path: ~/.nvm/versions/node/v18.17.1/bin/node
  Yarn:
    version: 4.3.1
    path: ~/.nvm/versions/node/v18.17.1/bin/yarn
  npm:
    version: 10.9.0
    path: ~/.nvm/versions/node/v18.17.1/bin/npm
  Watchman:
    version: 2024.04.08.00
    path: /opt/homebrew/bin/watchman
Managers:
  CocoaPods:
    version: 1.15.2
    path: /Users/pierrecaporossi/.rbenv/shims/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 24.0
      - iOS 18.0
      - macOS 15.0
      - tvOS 18.0
      - visionOS 2.0
      - watchOS 11.0
  Android SDK: Not Found
IDEs:
  Android Studio: 2024.1 AI-241.18034.62.2412.12266719
  Xcode:
    version: 16.0/16A242d
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.10
    path: /usr/bin/javac
  Ruby:
    version: 3.1.0
    path: /Users/pierrecaporossi/.rbenv/shims/ruby
npmPackages:
  "@react-native-community/cli": Not Found
  react:
    installed: 18.3.1
    wanted: 18.3.1
  react-native:
    installed: 0.75.4
    wanted: 0.75.4
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: false
iOS:
  hermesEnabled: true
  newArchEnabled: false
  • Platform that you're experiencing the issue on:
    • iOS
    • Android
    • iOS but have not tested behavior on Android
    • Android but have not tested behavior on iOS
    • Both
  • react-native-firebase version you're using that has this issue:
    • 21.0.0
  • Firebase module(s) you're using that has the issue:
    • Firebase Auth most likely
  • Are you using TypeScript?
    • Y


@PierreCapo
Copy link
Author

Curious to know if other people face this issue.
Given there is no potential race conditions and this problem happens for a small subset of users on different iPhones and iOS versions, I suspect some "infrastructure" related issue.

Thanks for your work the invertase team 🙏

@srimalfernando
Copy link

I am having the same issue.
My project also running on version 21.0.0

@craigthornton
Copy link

We are having this issue using Xamarin libraries. It only happens to a very small number of iOS users on newer iPhones

@youssdevx
Copy link

We also have this issue. We discussed this with Google support team and they told us that it could be related to "jailbroken devices"... Obviously, I don't think that this is the problem here.
I'm still waiting for Google to provide us with a real explanation

You need to check if you implemented App Attest correctly => https://rnfirebase.io/app-check/usage

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants