FlutterFlow supports two passwordless login methods: magic links via Firebase sendSignInLinkToEmail paired with Firebase Dynamic Links (email-based, no password), and SMS OTP via Firebase Phone Authentication (phone number + 6-digit code). Magic links require configuring Dynamic Links with your app's domain. Phone Auth works out of the box in FlutterFlow's built-in auth actions. Both methods must be enabled in the Firebase Authentication console first.
Two Paths to Passwordless Login in FlutterFlow
Removing passwords reduces sign-up friction and eliminates the most common support request: 'I forgot my password'. FlutterFlow supports two passwordless approaches. Magic links are ideal for web-focused apps — the user enters their email, clicks a link in their inbox, and is signed in. SMS OTP is better for mobile-first apps where users are on their phone and cannot easily switch to email. This tutorial implements both, including the Firebase Dynamic Links configuration that most magic link tutorials skip and that is the most common point of failure.
Prerequisites
- FlutterFlow project with Firebase connected
- Firebase Authentication console access
- For magic links: a custom domain or Firebase Hosting subdomain for Dynamic Links
- For SMS OTP: Firebase Blaze plan (phone auth requires billing enabled)
Step-by-step guide
Enable Email Link and Phone Authentication in Firebase
Enable Email Link and Phone Authentication in Firebase
Open the Firebase console and navigate to Authentication → Sign-in method. Click Email/Password and toggle the 'Email link (passwordless sign-in)' option on — this is separate from the standard Email/Password toggle. Also enable Phone as a sign-in provider if you want SMS OTP. Click Save after each change. Both of these must be enabled in the Firebase console before any code in FlutterFlow will work. Back in FlutterFlow, go to your project Settings → Firebase and confirm your Firebase config is up to date by clicking Sync Firebase.
Expected result: Email Link and Phone sign-in methods show as enabled (green) in the Firebase Authentication console's Sign-in method tab.
Configure Firebase Dynamic Links for magic link sign-in
Configure Firebase Dynamic Links for magic link sign-in
Magic links use Firebase Dynamic Links to redirect the user back to your app after they click the email link. In the Firebase console, go to Dynamic Links (under the Grow section). Click 'Get started' if you have not set it up before. Create a new URL prefix — this is the domain your links will use, like 'yourapp.page.link'. Under Link behavior, set both iOS and Android to 'Open the link in your app' and specify your app's bundle ID (iOS) and package name (Android). For web apps, set the fallback URL to your app's web URL. Note the full dynamic link domain — you will need it in your FlutterFlow action settings.
Expected result: A Dynamic Links URL prefix is created and appears in the Dynamic Links dashboard with your configured iOS and Android behavior.
Build the magic link sign-in flow in FlutterFlow
Build the magic link sign-in flow in FlutterFlow
Create a page named MagicLinkPage. Add an EmailTextField widget, a 'Send Magic Link' button, and a text confirmation message. Create a Custom Action named sendMagicLink. Inside the action, call FirebaseAuth.instance.sendSignInLinkToEmail(email: emailAddress, actionCodeSettings: settings) where settings specifies your Dynamic Link URL, the iOS and Android bundle IDs, and handleCodeInApp: true. Store the email in Secure Storage after sending so you can read it when the user returns via the link. Show a confirmation message ('Check your inbox') and disable the send button to prevent duplicate sends.
1// Custom Action: sendMagicLink2import 'package:firebase_auth/firebase_auth.dart';3import 'package:flutter_secure_storage/flutter_secure_storage.dart';45Future<bool> sendMagicLink(String email) async {6 const storage = FlutterSecureStorage();78 final actionCodeSettings = ActionCodeSettings(9 // Replace with your Dynamic Links domain or custom domain10 url: 'https://yourapp.page.link/signin?email=${Uri.encodeComponent(email)}',11 handleCodeInApp: true,12 iOSBundleId: 'com.yourcompany.yourapp',13 androidPackageName: 'com.yourcompany.yourapp',14 androidInstallApp: true,15 androidMinimumVersion: '21',16 );1718 try {19 await FirebaseAuth.instance.sendSignInLinkToEmail(20 email: email,21 actionCodeSettings: actionCodeSettings,22 );23 // Store email to retrieve when user returns via the link24 await storage.write(key: 'magic_link_email', value: email);25 return true;26 } catch (e) {27 return false;28 }29}Expected result: Entering an email and tapping 'Send Magic Link' sends a Firebase magic link email. The user's email is stored in Secure Storage for later retrieval.
Handle the magic link redirect when the user returns
Handle the magic link redirect when the user returns
When the user taps the magic link in their email, your app must detect the incoming link and complete the sign-in. Add a Custom Action named handleMagicLinkReturn to your app's root page (the first page loaded on startup). The action checks if the app was opened with a sign-in link using FirebaseAuth.instance.isSignInWithEmailLink(currentUrl). If it was, reads the stored email from Secure Storage, calls FirebaseAuth.instance.signInWithEmailLink(email: storedEmail, emailLink: currentUrl), and navigates the user to the home page. Handle the case where the stored email is missing — show a prompt asking the user to re-enter their email address.
1// Custom Action: handleMagicLinkReturn2import 'package:firebase_auth/firebase_auth.dart';3import 'package:flutter_secure_storage/flutter_secure_storage.dart';4import 'dart:html' as html show window;56Future<bool> handleMagicLinkReturn() async {7 final currentUrl = html.window.location.href;89 if (!FirebaseAuth.instance.isSignInWithEmailLink(currentUrl)) {10 return false; // Not a magic link redirect11 }1213 const storage = FlutterSecureStorage();14 String? email = await storage.read(key: 'magic_link_email');1516 if (email == null) {17 // User opened the link on a different device — email is unknown18 // In this case, show a UI prompt to re-enter their email19 return false;20 }2122 try {23 await FirebaseAuth.instance.signInWithEmailLink(24 email: email,25 emailLink: currentUrl,26 );27 await storage.delete(key: 'magic_link_email');28 return true; // Sign-in succeeded29 } catch (e) {30 return false;31 }32}Expected result: When a user opens the app via a magic link, they are automatically signed in and redirected to the home page without entering a password.
Implement SMS OTP login as an alternative to magic links
Implement SMS OTP login as an alternative to magic links
Firebase Phone Authentication provides a simpler passwordless option. In FlutterFlow, go to Authentication → Actions and look for the Phone Sign-In action. Add a page with a phone number TextField (use the Phone type with country code picker) and a 'Send OTP' button. Use FlutterFlow's built-in Phone Auth action — no custom code needed for the basic flow. After the user submits their number, Firebase automatically sends an SMS and shows the OTP verification screen. On iOS, App Attest verification is required for production; on Android, SafetyNet handles verification automatically. For the web, a reCAPTCHA appears before the SMS is sent.
Expected result: Users can enter their phone number, receive an SMS with a 6-digit code, enter the code, and be signed in — all within your FlutterFlow app without a password.
Complete working example
1// Standalone Custom Widget: MagicLinkSignInForm2// Handles both sending the magic link and the return URL check3import 'package:flutter/material.dart';4import 'package:firebase_auth/firebase_auth.dart';5import 'package:flutter_secure_storage/flutter_secure_storage.dart';67class MagicLinkSignInForm extends StatefulWidget {8 final VoidCallback onSignedIn;9 const MagicLinkSignInForm({Key? key, required this.onSignedIn}) : super(key: key);1011 @override12 State<MagicLinkSignInForm> createState() => _MagicLinkSignInFormState();13}1415class _MagicLinkSignInFormState extends State<MagicLinkSignInForm> {16 final _emailController = TextEditingController();17 bool _sent = false;18 bool _loading = false;19 String? _error;20 final _storage = const FlutterSecureStorage();2122 Future<void> _sendLink() async {23 final email = _emailController.text.trim();24 if (email.isEmpty || !email.contains('@')) {25 setState(() => _error = 'Please enter a valid email address');26 return;27 }2829 setState(() { _loading = true; _error = null; });3031 final settings = ActionCodeSettings(32 url: 'https://yourapp.page.link/signin?email=${Uri.encodeComponent(email)}',33 handleCodeInApp: true,34 iOSBundleId: 'com.yourcompany.yourapp',35 androidPackageName: 'com.yourcompany.yourapp',36 androidInstallApp: true,37 androidMinimumVersion: '21',38 );3940 try {41 await FirebaseAuth.instance.sendSignInLinkToEmail(42 email: email,43 actionCodeSettings: settings,44 );45 await _storage.write(key: 'magic_link_email', value: email);46 setState(() { _sent = true; _loading = false; });47 } catch (e) {48 setState(() {49 _error = 'Failed to send link. Please try again.';50 _loading = false;51 });52 }53 }5455 @override56 Widget build(BuildContext context) {57 if (_sent) {58 return Column(59 mainAxisSize: MainAxisSize.min,60 children: [61 const Icon(Icons.mark_email_read_outlined, size: 64, color: Colors.green),62 const SizedBox(height: 16),63 const Text('Check your inbox', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),64 const SizedBox(height: 8),65 Text('We sent a sign-in link to ${_emailController.text}'),66 ],67 );68 }6970 return Column(71 mainAxisSize: MainAxisSize.min,72 children: [73 TextFormField(74 controller: _emailController,75 keyboardType: TextInputType.emailAddress,76 decoration: const InputDecoration(labelText: 'Email address'),77 ),78 if (_error != null)79 Padding(80 padding: const EdgeInsets.only(top: 8),81 child: Text(_error!, style: const TextStyle(color: Colors.red)),82 ),83 const SizedBox(height: 16),84 SizedBox(85 width: double.infinity,86 child: ElevatedButton(87 onPressed: _loading ? null : _sendLink,88 child: _loading89 ? const SizedBox(height: 20, width: 20,90 child: CircularProgressIndicator(strokeWidth: 2))91 : const Text('Send Magic Link'),92 ),93 ),94 ],95 );96 }97}Common mistakes
Why it's a problem: Not configuring Firebase Dynamic Links before implementing magic link sign-in
How to avoid: Configure a Dynamic Links URL prefix in the Firebase console under Dynamic Links before writing any sign-in code. Pass this domain as the url parameter in ActionCodeSettings.
Why it's a problem: Not storing the user's email before sending the magic link
How to avoid: Store the email in Secure Storage immediately after calling sendSignInLinkToEmail. When handling the return link, read from Secure Storage. If the email is missing, prompt the user to re-enter it.
Why it's a problem: Calling handleMagicLinkReturn on every page load instead of only on app startup
How to avoid: Call handleMagicLinkReturn only from your app's first page (root route) On Page Load action, or use a Universal Links handler that fires only when the app is opened from a specific URL scheme.
Best practices
- Store the user's email in Secure Storage immediately before calling sendSignInLinkToEmail so sign-in can complete even on a different device.
- Check handleCodeInApp: true in ActionCodeSettings — without this, Firebase opens the link in a browser instead of your app.
- For SMS OTP, add test phone numbers in the Firebase console during development to avoid using real SMS quota.
- Show a 60-second cooldown on the magic link send button to prevent users from requesting multiple links in rapid succession.
- Always check FirebaseAuth.instance.isSignInWithEmailLink(url) before attempting to sign in with a URL to avoid errors on normal page loads.
- Handle the 'opened on different device' case by showing a one-field form asking users to re-enter their email when the stored email is not found.
- Monitor your Firebase Authentication usage dashboard after launch — phone authentication has SMS costs and unexpectedly high volume can affect your bill.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I am building a FlutterFlow app and want to add passwordless magic link sign-in using Firebase. Explain the full implementation including Dynamic Links configuration, sending the email link, storing the email locally, and handling the return redirect to complete sign-in.
Add passwordless email magic link authentication to my FlutterFlow app. Create a sign-in page where users enter their email, then handle the return link on app startup to complete sign-in using Firebase Dynamic Links and flutter_secure_storage to persist the email.
Frequently asked questions
What is the difference between magic links and SMS OTP for passwordless login?
Magic links send a clickable URL to the user's email — they click it and are signed in. SMS OTP sends a 6-digit code to their phone — they enter it in the app. Magic links are better for web and desktop-first users. SMS OTP is better for mobile-first apps where switching to email is inconvenient.
Do I need Firebase Dynamic Links for magic link sign-in?
Yes, for mobile apps. Dynamic Links ensure the magic link URL opens your app instead of a browser. Without them, tapping the link opens a generic Firebase page. For web-only apps, Dynamic Links are not required — the link opens the web app directly.
Are Firebase Dynamic Links being deprecated?
Yes. Firebase announced Dynamic Links deprecation with shutdown planned for August 2025. For new projects, use a custom domain with Firebase Hosting and handle the redirect with a custom URL scheme in your app. Check the Firebase console for updated recommendations.
Does Phone Authentication cost money in Firebase?
Phone Auth requires the Firebase Blaze (pay-as-you-go) plan. SMS messages cost approximately $0.01-0.06 per verification depending on the destination country. India and some other regions have higher SMS costs. Monitor usage in the Firebase console.
What happens if the user opens the magic link on a different device than where they requested it?
The email stored in Secure Storage is not available on the other device. Firebase will fail to complete sign-in without the email. Handle this by detecting the missing email and showing a prompt asking the user to re-enter their email address before completing the sign-in.
Can I use both passwordless and password-based login in the same app?
Yes. Enable both Email/Password and Email Link in Firebase Authentication settings. Some users may prefer to create a password account while others use magic links. Firebase handles both methods under the same user account when the same email is used.
How long is a magic link valid before it expires?
Firebase magic links expire after 1 hour by default. You cannot change this expiry time — it is set by Firebase's security infrastructure. If the user does not click the link within an hour, they need to request a new one.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation