Phone authentication hangs on iOS with @nativescript/firebase 5.0.2
Opened this issue · 3 comments
I have upgraded my app to the latest version of everything, namely NativeScript 8.9.2, @nativescript/firebase 5.0.2, macOS Sequoia 16.6.1, Xcode 16.4, etc. I can log in on Android devices and simulators (with test numbers) successfully, as I had before the upgrade. On iOS, however, the login hangs after entering the verification code from the SMS message. The Firebase UI completes, in that my login screen is displayed, but control is never returned. This is the code I use to initiate the login:
let idp = await firebase().ui().show({ providers: [new PhoneProvider()] });
In addition to the hang, I also receive the two silent notifications shown below.
app-root.js: handleMsg: message: {
"foreground": true,
"data": {
"com.google.firebase.auth": {
"warning": "This fake notification should be forwarded to Firebase Auth."
}
}
}
app-root.js:61 app-root.js: handleMsg: message: {
"data": {
"com.google.firebase.auth": "{\"receipt\":\"AEFDNu9LxA12I0A5Vch7JoTI8XiANEA-viqkiPhRykmcra6aVSCmjVZa5il-Yt4JUh3nbgRzhULnxdKSOEpMQUpcjWr72qkpx3p-ssSSemqDD8l33JdZqN-20Rl5RzU83A\",\"secret\":\"i9pLJ5Ol3WBB17Ol\"}"
},
"notification": {
"apple": {}
},
"contentAvailable": 1,
"foreground": true,
"messageId": "1757287521614594"
}
I found this information in the Firebase docs:
When you call verifyPhoneNumber(_:uiDelegate:completion:), Firebase sends a silent push notification to your app, or issues a reCAPTCHA challenge to the user. After your app receives the notification or the user completes the reCAPTCHA challenge, Firebase sends an SMS message containing an authentication code to the specified phone number and passes a verification ID to your completion function. You will need both the verification code and the verification ID to sign in the user.
I'm guessing that the secret property is the verification ID, but if so, I don't see how I can couple that with the verification code since they are on different threads.
Is anyone successfully using phone authentication with @nativescript/firebase 5.0.2?
I've included my package.json, build.xcconfig and Podfile contents below.
package.json
{
"name": "festivelo",
"main": "app/app.js",
"version": "1.0.0",
"private": true,
"dependencies": {
"@bradmartin/nativescript-urlhandler": "^2.0.1",
"@master.technology/permissions": "^2.0.1",
"@nativescript-community/ui-collectionview": "^6.0.4",
"@nativescript-community/ui-material-bottom-navigation": "^7.2.71",
"@nativescript/appversion": "^2.0.0",
"@nativescript/contacts": "^3.0.0",
"@nativescript/core": "~8.9.7",
"@nativescript/email": "^2.1.0",
"@nativescript/firebase-auth": "^5.0.2",
"@nativescript/firebase-core": "^5.0.2",
"@nativescript/firebase-database": "^5.0.2",
"@nativescript/firebase-messaging": "^5.0.2",
"@nativescript/firebase-ui": "5.0.2",
"@nativescript/geolocation": "^9.0.0",
"@nativescript/google-maps": "1.7.2",
"@nativescript/iqkeyboardmanager": "^2.1.2",
"@nativescript/pdf": "^2.0.0",
"@nativescript/theme": "^3.1.0",
"@triniwiz/nativescript-toasty": "^4.1.3",
"@wuilmerj24/store-update": "^1.0.7",
"assert": "^2.1.0",
"base-64": "^1.0.0",
"browserify-zlib": "^0.2.0",
"buffer": "^6.0.3",
"crypto-browserify": "^3.12.0",
"https-browserify": "^1.0.0",
"nativescript": "^8.9.3",
"nativescript-app-tour": "^4.0.0",
"nativescript-bitmap-factory": "^1.8.1",
"nativescript-clipboard": "^2.1.1",
"nativescript-danem-google-maps-utils": "^1.0.18",
"nativescript-drop-down": "6.0.2",
"nativescript-phone": "^3.0.3",
"nativescript-screenshot": "^0.0.2",
"nativescript-sqlite": "^2.8.6",
"nativescript-ui-listview": "^15.2.3",
"nativescript-ui-sidedrawer": "^15.2.3",
"os-browserify": "^0.3.0",
"patch-package": "^8.0.0",
"path": "^0.12.7",
"path-browserify": "^1.0.1",
"querystring-es3": "^0.2.1",
"stream-browserify": "^3.0.0",
"stream-http": "^3.2.0",
"tty-browserify": "^0.0.1",
"url": "^0.11.3"
},
"devDependencies": {
"@nativescript/android": "8.9.1",
"@nativescript/ios": "8.9.2",
"@nativescript/webpack": "^5.0.18",
"util": "^0.12.5"
},
"scripts": {
"postinstall": "patch-package"
}
}
build.xcconfig
// You can add custom settings here
// for example you can uncomment the following line to force distribution code signing
// CODE_SIGN_IDENTITY = iPhone Distribution
// To build for device with XCode you need to specify your development team.
DEVELOPMENT_TEAM = xxxxxxxxxx
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
TARGETED_DEVICE_FAMILY = 1;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
CODE_SIGN_ENTITLEMENTS = festivelo/festivelo.entitlements
// See https://stackoverflow.com/questions/66105038/referenceerror-metadata-for-googlemaps-gmsgeometrydistance-found-but-symbol-n
STRIPFLAGS=$(inherited) -s ${PROJECT_DIR}/../../App_Resources/iOS/exportedSymbols.txt
EXPORTED_SYMBOLS_FILE = ${PROJECT_DIR}/../../App_Resources/iOS/exportedSymbols.txt
// See https://github.com/msywensky/nativescript-phone/issues/51
OTHER_LDFLAGS = $(inherited) -framework MessageUI
// See https://github.com/NativeScript/NativeScript/issues/8860
EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64=arm64 arm64e armv7 armv7s armv6 armv8
EXCLUDED_ARCHS=$(inherited) $(EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_$(EFFECTIVE_PLATFORM_SUFFIX)__NATIVE_ARCH_64_BIT_$(NATIVE_ARCH_64_BIT))
Podfile
platform :ios, '15.0'
post_install do |installer|
installer.pods_project.targets.each do |target|
puts "Pods target name: " + target.name
# bypass xcode 16 error
# see https://stackoverflow.com/questions/78608693/boringssl-grpc-unsupported-option-g-for-target-arm64-apple-ios15-0
if target.name == 'BoringSSL-GRPC'
target.source_build_phase.files.each do |file|
if file.settings && file.settings['COMPILER_FLAGS']
flags = file.settings['COMPILER_FLAGS'].split
flags.reject! { |flag| flag == '-GCC_WARN_INHIBIT_ALL_WARNINGS' }
file.settings['COMPILER_FLAGS'] = flags.join(' ')
end
end
end
target.build_configurations.each do |config|
# suppress warnings
# see https://discord.com/channels/603595811204366337/828973466534936637/1192028592255799317
config.build_settings['GCC_WARN_INHIBIT_ALL_WARNINGS'] = 'YES'
# see https://github.com/NativeScript/NativeScript/issues/10034
if config.build_settings['WRAPPER_EXTENSION'] == 'bundle'
config.build_settings['DEVELOPMENT_TEAM'] = 'C424ZH53NJ'
end
# see https://github.com/NativeScript/NativeScript/issues/8860
# see https://www.jessesquires.com/blog/2020/07/20/xcode-12-drops-support-for-ios-8-fix-for-cocoapods/
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.0'
# see https://stackoverflow.com/questions/63607158/xcode-building-for-ios-simulator-but-linking-in-an-object-file-built-for-ios-f/63714000#63714000
config.build_settings["ONLY_ACTIVE_ARCH"] = "YES"
# see https://github.com/CocoaPods/CocoaPods/issues/12012
xcconfig_path = config.base_configuration_reference.real_path
xcconfig = File.read(xcconfig_path)
xcconfig_mod = xcconfig.gsub(/DT_TOOLCHAIN_DIR/, "TOOLCHAIN_DIR")
File.open(xcconfig_path, "w") { |file| file << xcconfig_mod }
if target.name == 'Realm'
create_symlink_phase = target.shell_script_build_phases.find { |x| x.name == 'Create Symlinks to Header Folders' }
create_symlink_phase.always_out_of_date = "1"
puts "target.name = Realm found"
end
# See https://stackoverflow.com/questions/61980618/getting-a-new-build-error-for-ios-nativescript
if ['Toast-Swift'].include? target.name
target.build_configurations.each do |config|
config.build_settings['SWIFT_VERSION'] = '5.3'
end
end
if ['FirebaseCoreInternal'].include? target.name
target.build_configurations.each do |config|
config.build_settings['SWIFT_VERSION'] = '5.3'
end
end
if ['FirebaseStorage'].include? target.name
target.build_configurations.each do |config|
config.build_settings['SWIFT_VERSION'] = '5.3'
end
end
# Hack to see if this works with Firebase 5.0.3
if ['FirebaseAuth'].include? target.name
target.build_configurations.each do |config|
config.build_settings['SWIFT_VERSION'] = '5.3'
end
end
if target.name.start_with? "GoogleDataTransport"
target.build_configurations.each do |config|
config.build_settings['CLANG_WARN_STRICT_PROTOTYPES'] = 'NO'
end
end
end
end
end
After chasing this for several days, I elected to skip @nativescript/firebase-ui and use @nativescript.firebase-auth directly. The good news is that this now works successfully on iOS devices. But, the bad news is that if there's an error and the user attempts to log in again, say for entering an incorrect verification code, then verifyPhoneNumber() hangs.
The purpose of the function below is to verify that the phone number entered belongs to the phone being used.
/**
* Use firebase phone authentication to verify phone number
* @global viewModel
* @return Promise true (success) or false (failure)
*/
async function verifyPhone() {
if (viewModel.user.phoneVerified) {
return true;
}
let verificationId = '';
let verificationCode = '';
try {
verificationId = await PhoneAuthProvider.provider().verifyPhoneNumber('+1' + viewModel.user.phone); // TODO: assumes US number
const result = await dialogs.prompt({
title: "Verification Code",
message: "Enter the 6-digit code sent to your phone.",
okButtonText: "Verify",
cancelButtonText: "Cancel",
inputType: 'number',
defaultText: "",
maxLength: 6,
});
if (result.result && result.text && result.text.length === 6) {
verificationCode = result.text;
} else {
console.log("login.verifyPhone: user cancelled or invalid code");
let message = result.result ? "Verification code must be 6 digits." : "User cancelled verification.";
throw new Error(message);
}
// Sign in with phone authentication to verify phone number, then delete newly-created user
const credential = PhoneAuthProvider.provider().credential(verificationId, verificationCode);
await firebase().auth().signInWithCredential(credential);
await firebase().auth().currentUser.delete(); // delete phone auth'ed user, replace w/ email auth
} catch (e) {
console.log("login.verifyPhone error: " + e);
await dialogs.alert({
title: "Log In",
message: "Your phone number could not be verified; you will need to try again. \n\n" + e,
okButtonText: "OK"
});
return false;
}
return true;
} // end verifyPhone
So, this is still a problem, but at least I can log in now on iOS (if I do so carefully).
After further testing, I've found that is after calling verifyPhoneNumber if you always call
const credential = PhoneAuthProvider.provider().credential(verificationId, verificationCode);
await firebase().auth().signInWithCredential(credential);
then subsequent log-in attempts will be successful. This limits meaningful error messages but at least it works. I'm still working on this so I'll keep the issue open.
My final conclusion is this: calling verifyPhoneNumber() twice in succession will cause a hang. If you call credential() after the first call verifyPhoneNumber(), then a subsequent call to verifyPhoneNumber() works as expected. You do not need to call firebase().auth().signInWithCredential if you already know there's an error or if the user tapped Cancel.
The original problem still exists - calling firebase().ui().show({ providers: [new PhoneProvider()] }) on iOS will cause a hang. But, the conclusion above allows for a bypass.