Flutterfire: [firebase_auth] Method swizzling problem with phone auth iOS

Created on 13 Oct 2019  Â·  32Comments  Â·  Source: FirebaseExtended/flutterfire

I'm having an issue with firebase_auth verifyPhoneNumber on iOS.
I followed all requirements on documentation. APN works fine (FCM can be received). But whenever verifyPhoneNumber called, it always fail with exception message:

If app delegate swizzling is disabled, remote notifications received by UIApplicationDelegate need to be forwarded to FIRAuth's canHandleNotification: method

I did pod upgrade to firebase_auth 6.3.0 and flutter to 1.7.11-pre43
still got the same problem. Android build works fine.

customer-response ios auth needs-repro bug documentation

Most helpful comment

@cielo
Hi, Yes i did. I got Token mismatch too. But, when i fixed AppDelegate.swift, i solved this error.

Here is my AppDelegate.swift Code

import UIKit
import Flutter
import Firebase
import FirebaseAuth
import UserNotifications
import FirebaseInstanceID

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
  ) -> Bool {
        FirebaseApp.configure()
        GeneratedPluginRegistrant.register(with: self)
        return true
  }
    override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        let firebaseAuth = Auth.auth()
        firebaseAuth.setAPNSToken(deviceToken, type: AuthAPNSTokenType.unknown)

    }
    override func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        let firebaseAuth = Auth.auth()
        if (firebaseAuth.canHandleNotification(userInfo)){
            print(userInfo)
            return
        }
    }
}

All 32 comments

@kroikie Hi, I think this should be an open issue.

@kroikie Hi, I think this should be an open issue.
According to the documentation I can set FirebaseAppDelegateProxyEnabled to NO, but it also disables FCM
https://firebase.google.com/docs/auth/ios/phone-auth#appendix:-using-phone-sign-in-without-swizzling

@collinjackson Seems like this is still an issue. I'll reopen here so we can track resolution efforts.

Firebase Auth + APN works on development mode but I get token mismatch when building release

Any solution regarding to this problem?

We had this problem for months, but it appears to have been fixed with the latest version (6.0.9). We used to get token mismatch error on iOS on release mode, but not anymore.

@umutseven92 I use firebase_messaging 6.0.9 for a while now, still there is an issue in IOS 12, I get token mismatch. It does work with the fix that someone made a long time ago: https://stackoverflow.com/questions/57395948/flutter-firebase-phone-auth-always-returns-token-mismatch-on-ios
but I want to see this fix implemented in new versions that also have other security fixes and such.

@umutseven92
I changed message version to firebase_messaging 6.0.9, but there exist same error..

I solved this problem. It was related APN setting(SandBox Token) not version problem.
Thanks :)

I solved this problem. It was related APN setting(SandBox Token) not version problem.
Thanks :)

@LeeLoHoon,

Were you able to get Firebase Phone Auth work in iOS with Firebase Cloud Messaging?
Both in Simulator & Real device?

Did you ever encounter Token mismatch error?

@cielo
Hi, Yes i did. I got Token mismatch too. But, when i fixed AppDelegate.swift, i solved this error.

Here is my AppDelegate.swift Code

import UIKit
import Flutter
import Firebase
import FirebaseAuth
import UserNotifications
import FirebaseInstanceID

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
  ) -> Bool {
        FirebaseApp.configure()
        GeneratedPluginRegistrant.register(with: self)
        return true
  }
    override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        let firebaseAuth = Auth.auth()
        firebaseAuth.setAPNSToken(deviceToken, type: AuthAPNSTokenType.unknown)

    }
    override func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        let firebaseAuth = Auth.auth()
        if (firebaseAuth.canHandleNotification(userInfo)){
            print(userInfo)
            return
        }
    }
}

@LeeLoHoon Thanks. I finally got it to work in a different way, but it works now. Cheers

@cielo
My pleasure :)

@cielo what was the different way that you made it work?

Well, I do not have any APN setting overrides in my project.

For me,

  1. I re-created iOS App registered in Firebase console,
  2. updated FirebaseAuth/FirebaseCore/FirebaseMessaging to latest versions
  3. re-created & uploaded APN Auth Authentication Key in Firebase Console Settings - Cloud Messaging.

This made Firebase iOS Phone Authentication to work in both built for development/production in real device.

Also, I do not know if it matters, but I am using Apple Development for Code Signing Identity.

However, in iOS simulator, I am still having a phone verification issue with error like below for reCAPCHA workflow.

2020-02-02 23:47:32.469357-0800 Runner[83534:726911] flutter: verifyPhoneNumberError - {"error":{"code":403,"message":"Requests from this ios client application <empty> are blocked.","errors":[{"message":"Requests from this ios client application <empty> are blocked.","domain":"global","reason":"forbidden"}],"status":"PERMISSION_DENIED"}}

My API Key Credentials is restricted to my application bundle id (set in Cloud Console), and above error gets thrown during reCAPTCHA workflow. When my API Key is unrestricted, I do not encounter above error.

So something fishy is going on in firebase_auth such that it is not sending bundle id information to reCAPCHA endpoint. For this issue, I have created a separate issue.

@Antonimo In your case, does iOS simulator reCAPTCHA flow work as expected?

ios simulator works only with preconfigured test phone numbers, for me that works fine.

@Antonimo I see. Is your iOS API key (from Google Cloud console) restricted to your app identified by bundle id? or is it unrestricted at this moment?

My phone auth with iOS simulator works fine if it is unrestricted, but it does not if API key is restricted.

Hello! i was wondering, is this something current? (as in the latest flutter release )I am having the same issue and @LeeLoHoon solution seems to worked out. Should I do this every time I'm deploying on iOS ?

@sebastianalcala
Hi, My solution is for dividing Prod Firebase and Development Firebase.
https://medium.com/rocket-fuel/using-multiple-firebase-environments-in-ios-12b204cfa6c0
Here is reference for different Firebase IOS setting.
I recommend Separate Firebase Environments — An Alternate Approach

import UIKit
import Flutter
import Firebase
import FirebaseAuth
import UserNotifications
import FirebaseInstanceID

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
) -> Bool {
FirebaseApp.configure()
GeneratedPluginRegistrant.register(with: self)
return true
}
override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let firebaseAuth = Auth.auth()
firebaseAuth.setAPNSToken(deviceToken, type: AuthAPNSTokenType.sandbox)

}
override func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    let firebaseAuth = Auth.auth()
    if (firebaseAuth.canHandleNotification(userInfo)){
        print(userInfo)
        return
    }
}

}

Hi @LeeLoHoon - I tried this solution but I am getting this error:

..../ios/Runner/AppDelegate.swift:4:8: error: no such module 'FirebaseAuth'
import FirebaseAuth

Did you try above thing?
Then, try this and do it again.
https://stackoverflow.com/questions/41709912/error-could-not-build-objective-c-module-firebase

Yes! I tried your solution and fixed the import error also from the link you have suggested but unfortunately, still, the auto verification is not happening :(

I tried the above solutions and it seemed to work but then a client with iPhone 7- iOS 13.3.1 got token mismatch

Here is a process when you want to use dev DB and prod DB.
1.For auto switching, refer to this page's Separate Firebase Environments — An Alternate Approach
https://medium.com/rocket-fuel/using-multiple-firebase-environments-in-ios-12b204cfa6c0

2.Change AppDelegate.swift code with this.

import UIKit
import Flutter
import Firebase
import FirebaseAuth
import UserNotifications
import FirebaseInstanceID

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
  ) -> Bool {
        FirebaseApp.configure()
        GeneratedPluginRegistrant.register(with: self)
        return true
  }
    override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        let firebaseAuth = Auth.auth()
        firebaseAuth.setAPNSToken(deviceToken, type: AuthAPNSTokenType.unknown)

    }
    override func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        let firebaseAuth = Auth.auth()
        if (firebaseAuth.canHandleNotification(userInfo)){
            print(userInfo)
            return
        }
    }
}

  1. If you have an error with Firebase import or something, refer to this page.
    https://stackoverflow.com/questions/41709912/error-could-not-build-objective-c-module-firebase

These are everything I can give you.

Here is a process when you want to use dev DB and prod DB.
1.For auto switching, refer to this page's Separate Firebase Environments — An Alternate Approach
https://medium.com/rocket-fuel/using-multiple-firebase-environments-in-ios-12b204cfa6c0

2.Change AppDelegate.swift code with this.

import UIKit
import Flutter
import Firebase
import FirebaseAuth
import UserNotifications
import FirebaseInstanceID

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
  ) -> Bool {
        FirebaseApp.configure()
        GeneratedPluginRegistrant.register(with: self)
        return true
  }
    override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        let firebaseAuth = Auth.auth()
        firebaseAuth.setAPNSToken(deviceToken, type: AuthAPNSTokenType.unknown)

    }
    override func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        let firebaseAuth = Auth.auth()
        if (firebaseAuth.canHandleNotification(userInfo)){
            print(userInfo)
            return
        }
    }
}
  1. If you have an error with Firebase import or something, refer to this page.
    https://stackoverflow.com/questions/41709912/error-could-not-build-objective-c-module-firebase

These are everything I can give you.

Using that firebase works but it breaks onesignal, any thoughts ?

This is what works with onesignal

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

@cielo
Hi, Yes i did. I got Token mismatch too. But, when i fixed AppDelegate.swift, i solved this error.

Here is my AppDelegate.swift Code

import UIKit
import Flutter
import Firebase
import FirebaseAuth
import UserNotifications
import FirebaseInstanceID

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
  ) -> Bool {
        FirebaseApp.configure()
        GeneratedPluginRegistrant.register(with: self)
        return true
  }
    override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        let firebaseAuth = Auth.auth()
        firebaseAuth.setAPNSToken(deviceToken, type: AuthAPNSTokenType.unknown)

    }
    override func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        let firebaseAuth = Auth.auth()
        if (firebaseAuth.canHandleNotification(userInfo)){
            print(userInfo)
            return
        }
    }
}

Thanks for this! This worked for me. Annoying that the documentation for the plugin has the incorrect setup code and I had to dig to find it here.

The other hard thing is before I put this change in, things worked fine in Android and in iOS simulator, but not on iOS physical device.

If your flutter Runner app is in Objective-C and @import FirebaseAuth; doesn't work even after a pods install and you want your hobby project to "just run already":

-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    id auth = [NSClassFromString(@"FIRAuth") performSelector:@selector(auth)];
    NSLog(@"didRegisterForRemoteNotificationsWithDeviceToken auth: %@", auth);
    [auth setAPNSToken:deviceToken type:FIRAuthAPNSTokenTypeUnknown];
}

-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
    id auth = [NSClassFromString(@"FIRAuth") performSelector:@selector(auth)];
    if ([auth canHandleNotification:userInfo]) {
        NSLog(@"didReceiveRemoteNotification userInfo: %@", userInfo);
    } else {
        NSLog(@"didReceiveRemoteNotification could not handle notification");
    }
}

I tried out what @LeeLoHoon said above, but it did not work for me until I made the following changes -

override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            FirebaseApp.configure()
            GeneratedPluginRegistrant.register(with: self)

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

    override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        let firebaseAuth = Auth.auth()
        firebaseAuth.setAPNSToken(deviceToken, type: AuthAPNSTokenType.prod)
    }

    override func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        let firebaseAuth = Auth.auth()

        if (firebaseAuth.canHandleNotification(userInfo)){
           completionHandler(.noData)
            return
        }

    }

basically on canHandleNotification one needs to call the completionHandler. This worked for me.

I think there is something underlying that is related to the firebase_messaging issue I linked above. This has plagued me for a long time now and i can't seem to narrow down what fixes/breaks it.

I may have found a winning combination of things to let firebase_auth and firebase_messaging work finally.

Contrary to what all of the github issues say, do add this to your AppDelegate.swift didFinishLaunchingWithOptions:

 if #available(iOS 10.0, *) {
    UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}

Do not add this to your Info.plist:

FirebaseAppDelegateProxyEnabled
NO

Do add this to AppDelegate.swift (just like the solution above from @abhriyaroy):

    override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        let firebaseAuth = Auth.auth()
        firebaseAuth.setAPNSToken(deviceToken, type: AuthAPNSTokenType.unknown)
    }

    override func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        let firebaseAuth = Auth.auth()
        if (firebaseAuth.canHandleNotification(userInfo)){
            // print(userInfo)
            completionHandler(.noData)
            return
        }
    }

That should solve it. Firebase auth should be able to use the push notification verification prior to requesting notification permission, and after requesting notification permission firebase messaging should work now both in the foreground and background.

I think there is some kind of timeout related to Firebase here because while testing this, I can't seem to "break" it by setting it back, it just keeps working but after a few days it will be broken again.

I just wish I knew why this requires hacking up AppDelegate.swift like this, and I wish it worked out of the box following the instructions on pub.dev. I have spent so many hours on this issue pulling my hair out.

Do not add this to your Info.plist:
FirebaseAppDelegateProxyEnabled
NO

But this is only possible if firebase messaging is the only notification plugin, right? So if I further want to use the flutter_local_notification plugin this is not applicable

Do not add this to your Info.plist:
FirebaseAppDelegateProxyEnabled
NO

But this is only possible if firebase messaging is the only notification plugin, right? So if I further want to use the flutter_local_notification plugin this is not applicable

I haven't tested, but I think if you want to do that, then you have to use this for didReceiveRemoteNotification:

override func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
  let firebaseAuth = Auth.auth()
  if (firebaseAuth.canHandleNotification(userInfo)){
      // print(userInfo)
      completionHandler(.noData)
      return
  }

  // https://firebase.google.com/docs/cloud-messaging/ios/receive#handle_messages
  Messaging.messaging().appDidReceiveMessage(userInfo)
}

Hey all 👋

Our rework of the firebase_auth plugin as part of the FlutterFire roadmap was published over a week ago with a ton of fixes and new features. Please could you try the new version and see if this is still an issue for you? If it is then please submit a new up to date GitHub issue so it's easier to track.

For help migrating to the new plugins please see the new migration guide: https://firebase.flutter.dev/docs/migration

Was this page helpful?
0 / 5 - 0 ratings