What changed after Dynamic Links
- The link is on your Hosting domain. With current SDKs, the email links to
PROJECT_ID.firebaseapp.com, or to a custom Firebase Hosting domain you name withlinkDomain.dynamicLinkDomainis deprecated: don’t set it. - Your app has to claim that domain. On iOS through Associated Domains, on Android through a verified App Link. Without it, the link opens in the browser.
- Old SDKs stopped working. Apps on the Apple SDK before 11.8.0 or the Android Auth SDK before 23.2.0 send Dynamic Links, which no longer resolve. Flutter and React Native wrappers need versions built on those SDKs.
- New projects don’t allow localhost. Projects created after 28 April 2025 don’t list
localhostas an authorised domain, so a continue URL onlocalhostis refused until you add it.
The setup to test
Send the link with handleCodeInApp on, your continue URL, and both app IDs. On iOS:
let settings = ActionCodeSettings()
settings.url = URL(string: "https://example.com/finish-sign-in")
settings.handleCodeInApp = true
settings.setIOSBundleID(Bundle.main.bundleIdentifier!)
settings.setAndroidPackageName("com.example.android", installIfNotAvailable: false, minimumVersion: "12")
try await Auth.auth().sendSignInLink(toEmail: email, actionCodeSettings: settings)
UserDefaults.standard.set(email, forKey: "Email")
On Android:
val settings = actionCodeSettings {
url = "https://example.com/finish-sign-in"
handleCodeInApp = true
setIOSBundleId("com.example.ios")
setAndroidPackageName("com.example.android", true, "12")
}
Firebase.auth.sendSignInLinkToEmail(email, settings)
The continue URL’s domain must be in Authentication → Settings → Authorized domains. Then:
- iOS: add
applinks:PROJECT_ID.firebaseapp.com(or your custom Hosting domain) under Signing & Capabilities → Associated Domains. - Android: add an intent filter with
android:autoVerify="true", theVIEWaction, theDEFAULTandBROWSABLEcategories, schemehttps, hostPROJECT_ID.firebaseapp.com(or your custom domain) andpathPrefix/__/auth/links. Register the app’s SHA-1 and SHA-256 fingerprints in the Firebase console.
When the link opens the app, check it and sign in with the saved address: Auth.auth().isSignIn(withEmailLink:) and signIn(withEmail:link:) on iOS, isSignInWithEmailLink(link) and signInWithEmailLink(email, link) on Android. Firebase’s guides for iOS and Android have the details.
Test it end to end
- Create an address in Proxy Mail QA and enter it in your app’s sign-in screen in the simulator or emulator.
- The link arrives on the address’s row as a sign-in link. Nothing has opened it.
- Open it on the device, not in your desktop browser:
xcrun simctl openurl booted '<link>'on iOS,adb shell "am start -W -a android.intent.action.VIEW -d '<link>'"on Android. The app should open, not the browser. - Check you’re signed in as that address, and land where your app sends people after signing in.
In a UI test, wait for a link of type magic_signin with the helper from the mobile guides, then open it with XCUIApplication().open, an ACTION_VIEW intent, Maestro’s openLink or Detox’s device.openURL.
What to check
- Same device, app closed. Open the link with the app not running: it should launch, complete sign-in without asking for the address, and land on the right screen.
- Same device, app open. Open it while the app is on the sign-in screen.
- Another device, or after reinstalling. The saved address is gone, so the app must ask for it, and refuse a different address with a clear message.
- Twice. The second time, the link should be refused as already used, with a way to request a new one.
- The continue URL. If it points at
localhostor10.0.2.2, Proxy Mail QA flags it, even inside Firebase’s mobile link. See links that point at localhost.
Platform setup beyond Firebase: iOS, Android, React Native and Expo and Flutter.