Repository navigation
Conversation
Auth's own app and scene delegate interceptors consume reCAPTCHA, MFA and OAuth redirect URLs before Flutter asks the plugin, so Auth.canHandle then returned false and Flutter pushed the URL as a deep link. The plugin now claims any Auth callback URL (app-<GOOGLE_APP_ID> or reversed client ID scheme, host firebaseauth, path /link).
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. |
The URL handlers returned false before matching when no [DEFAULT] app was configured, so iOS apps that only use Firebase.initializeApp(name:) with FirebaseAuth.instanceFor(app:) still had reCAPTCHA, MFA and OAuth redirects pushed as Flutter deep links. The FirebaseApp.app() check now only guards Auth.auth().canHandle, and the callback URL match runs regardless. The e2e tests now also cover the reversed client ID scheme (skipped when the app has no iOS client ID) and the named-app case, and wait past the control deep link instead of relying on deep links arriving in order.
Description
On iOS, the reCAPTCHA, multi-factor and OAuth redirects (
app-<GOOGLE_APP_ID>://firebaseauth/link?deep_link_id=…) reach Flutter's deep linking, which is on by default since Flutter 3.27. With go_router, the app shows a "no routes for location /link?deep_link_id=…" page, although sign-in still succeeds.Cause.
FirebaseApp.configure()creates Auth eagerly, and Auth registers itself as a GoogleUtilities app delegate and scene delegate interceptor. When the redirect comes back, the interceptor runs first and consumes the URL:AuthURLPresenter.canHandlefinishes the presentation and clears its callback matcher. Flutter then asks the plugin, whoseAuth.auth().canHandle(url)now returnsfalse, so Flutter pushes the URL as a deep link. This happens on both paths:FlutterAppDelegate application:openURL:options:→ plugin returnsfalse→sendDeepLinkToFramework;FlutterSceneDelegate scene:openURLContexts:→ plugin returnsfalse→ the app delegate fallback skips the plugin (it conforms toFlutterSceneLifeCycleDelegate) → deep link.Fix. The plugin's
application(_:open:options:)andscene(_:openURLContexts:)also returntruefor any Auth callback URL: hostfirebaseauth, path/link, and the scheme the SDK builds redirects with (app-+GOOGLE_APP_IDwith:replaced by-, or the reversedCLIENT_ID) of any configured app. This also works in apps that only configure named apps (Firebase.initializeApp(name:)+FirebaseAuth.instanceFor(app:)): theFirebaseApp.app() != nilcheck now only guardsAuth.auth().canHandle, not the callback URL match. Other URLs are unaffected.Not changed: the audit also said
scene(_:openURLContexts:)isn't@objc, so Flutter never calls it. That doesn't reproduce. Swift infers@objcfrom theFlutterSceneLifeCycleDelegateconformance: the compiled plugin has thescene:openURLContexts:ObjC thunk, and the scene tests below only pass because Flutter calls that method.Known limits:
scene(_:openURLContexts:)claims the whole set when any URL in it matches, so a non-Auth URL delivered in the same set gets no deep link. The previouscanHandlecode had the same shape.FirebaseApp.app()guard is already onmain, somainsupersedes it.Tests
iOS e2e in the auth example (
integration_test/auth_callback_url_e2e_test.dart, run bye2e_test.dartine2e-auth). A test-only channel (example/ios/Runner/AuthCallbackURLTestChannel.m, likeRemoteNotificationTestChannel.swiftin messaging) opens a URL the way iOS does, throughUIApplication.delegate'sapplication:openURL:options:or the scene delegate'sscene:openURLContexts:. The Dart side records what Flutter deep linking pushes.openURLreturningtrue. The example uses UIScene, soFlutterAppDelegatehas no root view controller and never pushes what it opens; the deep link check there cannot fail.[DEFAULT]app's options; the reversed client ID tests are skipped, with a reason, when the options have no iOS client ID.FirebaseApp.app()returnnilwhile it opens that app's callback URL. It does not delete[DEFAULT], which would replace its nativeAuthand drop the emulator settings for the rest of the suite.mainapp-scheme:openURLreturnstruefalseapp-scheme: not pushedopenURLreturnstruefalseopenURLreturnstruefalsefalseThe reversed client ID rows used a local client ID; with the example's placeholder options those tests are skipped. I ran the full
e2e_test.dartlocally against the Auth emulator with this change: the six new tests pass. The failures are thevalidatePassword()tests, which need the live API, andupdatePhotoURL can set the photoURL to null, which failed the same way on the first commit and is in code this PR doesn't touch. I built with Swift Package Manager; the CocoaPods build CI uses isn't verified locally.Related Issues
Related to #17035 (closed; its workaround was
FlutterDeepLinkingEnabled=false).Checklist
///).melos run analyze) does not report any problems on my PR.Breaking Change