Skip to content

[πŸ”₯πŸ›] httpsCallable in RNFBFunctions.mm Incorrectly Maps FIRAuthErrorDomain to Generic "Network Error" or "Internal Error"Β #8881

@jerling2

Description

@jerling2

Issue

Background: The iOS Keychain holds onto credentials even after an app is uninstalled or a local environment is reset. In development, this creates a "zombie session" where the client attempts an handshake with the Firebase Auth Emulator using stale credentials that no longer exist in the emulator's volatile memory.

Issue: When these "zombie" handshakes fail, React Native Firebase (RNFB) maps the underlying native FIRAuthErrorDomain to generic UNKNOWN or INTERNAL_ERROR codes in RNFBFunctions.mm. This obfuscates the true cause of the failure.

Steps to Reproduce:

  1. Start Firebase Auth and Functions emulator

  2. On an iOS simulator/device, sign in a user (e.g., via "Suggested Password"). The Keychain now stores these credentials.

  3. Manually delete the user from the Auth Emulator UI (or restart the emulator) without calling signOut() on the client.

  4. Attempt to call an httpsCallable Cloud Function.

  • If the Auth Emulator is unreachable:
    • RNFBFunctions.mm catches an FIRAuthErrorDomain error but returns a generic "Network Error"
  • If the Auth Emulator is reachable:
    • RNFBFunctions.mm catches an FIRAuthErrorDomain error but returns a generic "Internal Error"

Result: Generic errors, such as "Network Error" or "Internal Error," can mislead developers into believing the issue lies within their source code or Firebase Functions configuration. In reality, the underlying native error correctly identifies a disconnected Auth emulator or an invalid authentication handshakeβ€”details that are unfortunately obscured by the React Native bridge.

Work Around: If a Cloud Function is reachable via curl or fetch but fails when called from the iOS Simulator, use "Erase All Content and Settings" on the simulator. This fully wipes the system Keychain and removes the "zombie" credentials causing the conflict.

Preventable Steps: To avoid this state, always explicitly sign out development users before resetting the emulator environment, or disable Keychain during the development.

RNFBFunctionsModule.mm - lines 42:103

- (void)httpsCallable:(NSString *)appName
               region:(NSString *)customUrlOrRegion
         emulatorHost:(NSString *_Nullable)emulatorHost
         emulatorPort:(double)emulatorPort
                 name:(NSString *)name
                 data:(JS::NativeRNFBTurboFunctions::SpecHttpsCallableData &)data
              options:(JS::NativeRNFBTurboFunctions::SpecHttpsCallableOptions &)options
              resolve:(RCTPromiseResolveBlock)resolve
               reject:(RCTPromiseRejectBlock)reject {
  NSURL *url = [NSURL URLWithString:customUrlOrRegion];
  FIRApp *firebaseApp = [RCTConvert firAppFromString:appName];

  // πŸ› οΈ Setup - No issue
  // ⏩ ... skipped for brevity

  // πŸš¨πŸ‘‡ Issue in the handle Reject/Resolve 'Promise' block.
  [callable callWithObject:callableData
    completion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) {
        if (error) {
         
        /* ℹ️ (Examples of errors I got here)
        -- If auth emulator is not running, and the user is in a zombie session...

            domain: @"FIRAuthErrorDomain"
            Error: NETWORK_ERROR 
            Code: 17020
            LocalizedDescription: "A network error (such as timeout, 
            interrupted connection or unreachable host) has occured." 
        
        -- If auth emulator is running, but the user is in a zombie session...

            domain: @"FIRAuthErrorDomain"
            Error: INVALID_REFRESH_TOKEN 
            Code: 17999
            LocalizedDescription: "An internal error has occurred, print
            and inspect the error details for more information." 
        */

        NSObject *details = [NSNull null];
        NSString *message = error.localizedDescription;
        NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];

        //  πŸš¨πŸ‘‡ Only errors from this domain are handled.
        if ([error.domain isEqual:@"com.firebase.functions"]) {
            details = error.userInfo[@"details"];
            if (details == nil) {
            details = [NSNull null];
            }
        }

        userInfo[@"code"] = [self getErrorCodeName:error]; //< ❌ incorrect
        userInfo[@"message"] = message; //< βœ… correct, but not very helpful.
        userInfo[@"details"] = details; //< ❌ incorrect

        /* πŸ› (Stripped error messages)
        
        -- If auth emulator is not running, and the user is in a zombie session...

        code: UKNOWN
        message: "A network error ... has occured"
        details: ''

        -- If auth emulator is running, but the user is in a zombie session...

        code: UKNOWN
        message: "An internal error has occurred ..."
        details: ''
        */

        [RNFBSharedUtils rejectPromiseWithUserInfo:reject userInfo:userInfo];
        } else {
        resolve(@{@"data" : [result data]});
        }
    }];
}

RNFBFunctionsModule.mm - lines 171:242

- (NSString *)getErrorCodeName:(NSError *)error {
  
  // 🚨 if error.domain = @"FIRAuthErrorDomain" β‰  "@com.firebase.functions"

  NSString *code = @"UNKNOWN";
  if ([error.domain isEqual:@"com.firebase.functions"]) {
    // ⏩ ...skipped
  }
  return code; //< ❌ @"UKNOWN"
}

Project Files

  • iOS: RNFBFunctionsModule.mm contents.

Javascript

Click To Expand

package.json:

# N/A

firebase.json for react-native-firebase v6:

# N/A

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")
require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")

require 'json'
podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {}

def ccache_enabled?(podfile_properties)
  # Environment variable takes precedence
  return ENV['USE_CCACHE'] == '1' if ENV['USE_CCACHE']
  
  # Fall back to Podfile properties
  podfile_properties['apple.ccacheEnabled'] == 'true'
end

ENV['RCT_NEW_ARCH_ENABLED'] ||= '0' if podfile_properties['newArchEnabled'] == 'false'
ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] ||= podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR']
ENV['RCT_USE_RN_DEP'] ||= '1' if podfile_properties['ios.buildReactNativeFromSource'] != 'true' && podfile_properties['newArchEnabled'] != 'false'
ENV['RCT_USE_PREBUILT_RNCORE'] ||= '1' if podfile_properties['ios.buildReactNativeFromSource'] != 'true' && podfile_properties['newArchEnabled'] != 'false'
platform :ios, podfile_properties['ios.deploymentTarget'] || '15.1'

prepare_react_native_project!

target 'MyProject' do
  use_expo_modules!

  if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1'
    config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"];
  else
    config_command = [
      'node',
      '--no-warnings',
      '--eval',
      'require(\'expo/bin/autolinking\')',
      'expo-modules-autolinking',
      'react-native-config',
      '--json',
      '--platform',
      'ios'
    ]
  end

  config = use_native_modules!(config_command)

  use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']
  use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']

  use_react_native!(
    :path => config[:reactNativePath],
    :hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes',
    # An absolute path to your application root.
    :app_path => "#{Pod::Config.instance.installation_root}/..",
    :privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false',
  )

  post_install do |installer|
    react_native_post_install(
      installer,
      config[:reactNativePath],
      :mac_catalyst_enabled => false,
      :ccache_enabled => ccache_enabled?(podfile_properties),
    )
  end
end

AppDelegate.m:

import Expo
import FirebaseCore
import React
import ReactAppDependencyProvider

@UIApplicationMain
public class AppDelegate: ExpoAppDelegate {
  var window: UIWindow?

  var reactNativeDelegate: ExpoReactNativeFactoryDelegate?
  var reactNativeFactory: RCTReactNativeFactory?

  public override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
  ) -> Bool {
    let delegate = ReactNativeDelegate()
    let factory = ExpoReactNativeFactory(delegate: delegate)
    delegate.dependencyProvider = RCTAppDependencyProvider()

    reactNativeDelegate = delegate
    reactNativeFactory = factory
    bindReactNativeFactory(factory)

#if os(iOS) || os(tvOS)
    window = UIWindow(frame: UIScreen.main.bounds)
// @generated begin @react-native-firebase/app-didFinishLaunchingWithOptions - expo prebuild (DO NOT MODIFY) sync-10e8520570672fd76b2403b7e1e27f5198a6349a
FirebaseApp.configure()
// @generated end @react-native-firebase/app-didFinishLaunchingWithOptions
    factory.startReactNative(
      withModuleName: "main",
      in: window,
      launchOptions: launchOptions)
#endif

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

  // Linking API
  public override func application(
    _ app: UIApplication,
    open url: URL,
    options: [UIApplication.OpenURLOptionsKey: Any] = [:]
  ) -> Bool {
    return super.application(app, open: url, options: options) || RCTLinkingManager.application(app, open: url, options: options)
  }

  // Universal Links
  public override func application(
    _ application: UIApplication,
    continue userActivity: NSUserActivity,
    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
  ) -> Bool {
    let result = RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
    return super.application(application, continue: userActivity, restorationHandler: restorationHandler) || result
  }
}

class ReactNativeDelegate: ExpoReactNativeFactoryDelegate {
  // Extension point for config-plugins

  override func sourceURL(for bridge: RCTBridge) -> URL? {
    // needed to return the correct URL for expo-dev-client.
    bridge.bundleURL ?? bundleURL()
  }

  override func bundleURL() -> URL? {
#if DEBUG
    return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry")
#else
    return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
#endif
  }
}


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 26.2
  CPU: (8) arm64 Apple M1
  Memory: 126.69 MB / 16.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 22.12.0
    path: /Users/<user>/.nvm/versions/node/v22.12.0/bin/node
  Yarn: Not Found
  npm:
    version: 11.0.0
    path: /Users/<user>/.nvm/versions/node/v22.12.0/bin/npm
  Watchman: Not Found
Managers:
  CocoaPods:
    version: 1.16.2
    path: /opt/homebrew/bin/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 25.2
      - iOS 26.2
      - macOS 26.2
      - tvOS 26.2
      - visionOS 26.2
      - watchOS 26.2
  Android SDK: Not Found
IDEs:
  Android Studio: 2025.2 AI-252.28238.7.2523.14688667
  Xcode:
    version: 26.2/17C52
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 21.0.10
    path: /opt/homebrew/Cellar/openjdk@21/21.0.10/libexec/openjdk.jdk/Contents/Home/bin/javac
  Ruby:
    version: 2.6.10
    path: /usr/bin/ruby
npmPackages:
  "@react-native-community/cli":
    installed: 20.1.1
    wanted: ^20.1.1
  react:
    installed: 19.1.0
    wanted: 19.1.0
  react-native:
    installed: 0.81.5
    wanted: 0.81.5
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: true
iOS:
  hermesEnabled: true
  newArchEnabled: true
  • 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:
    • 23.8.4
  • Firebase module(s) you're using that has the issue:
    • @react-native-firebase/functions
    • @react-native-firebase/auth
  • Are you using TypeScript?
    • N


Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions