Flutter基于Firebase实现带手机号OTP验证的邮箱密码注册登录功能开发求助
Hey there! Let's walk through how to build this enhanced registration and login flow with Firebase in your Flutter app—exactly what you're asking for. I'll break it down into clear, actionable steps with code snippets to make it straightforward:
First, ensure you have the necessary packages in your pubspec.yaml (you might already have some for email/password auth, but add the missing ones):
dependencies: flutter: sdk: flutter firebase_core: ^2.15.0 firebase_auth: ^4.10.0 cloud_firestore: ^4.8.3 # Optional: For a clean OTP input UI, use this package flutter_otp_text_field: ^1.1.3
Run flutter pub get after updating, and confirm Firebase is initialized in your main function (you've probably done this already, but just in case):
void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(); runApp(const MyApp()); }
Your registration has two core phases: collecting user details, then verifying the phone number via OTP.
Step 2.1: Collect Details & Create Initial User
Build a UI to gather email, password, and phone number (remind users to include the country code, e.g., +1234567890). When the user taps "Register", execute this code:
final auth = FirebaseAuth.instance; final firestore = FirebaseFirestore.instance; // Get values from text controllers final email = emailController.text.trim(); final password = passwordController.text.trim(); final phoneNumber = phoneController.text.trim(); try { // Create the user with email/password first final userCredential = await auth.createUserWithEmailAndPassword( email: email, password: password, ); final userId = userCredential.user!.uid; // Store user data in Firestore (mark phone as unverified initially) await firestore.collection('users').doc(userId).set({ 'email': email, 'phoneNumber': phoneNumber, 'phoneVerified': false, 'createdAt': FieldValue.serverTimestamp(), }); // Send OTP and navigate to verification screen await sendOtp(phoneNumber); Navigator.push( context, MaterialPageRoute( builder: (_) => OtpVerificationScreen(userId: userId, phone: phoneNumber) ) ); } on FirebaseAuthException catch (e) { // Handle errors like duplicate email or weak password ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.message!))); }
Step 2.2: Send OTP to the Phone Number
Implement the sendOtp function to trigger Firebase's phone authentication:
String verificationId = ''; // Store this globally or pass via state management Future<void> sendOtp(String phoneNumber) async { final auth = FirebaseAuth.instance; await auth.verifyPhoneNumber( phoneNumber: phoneNumber, verificationCompleted: (PhoneAuthCredential credential) async { // Auto-verification (rare case, handle it anyway) await verifyOtpAndCompleteRegistration(credential, userId); }, verificationFailed: (FirebaseAuthException e) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.message!))); }, codeSent: (String id, int? resendToken) { verificationId = id; // Save this to use for OTP verification }, codeAutoRetrievalTimeout: (String id) { verificationId = id; }, timeout: const Duration(seconds: 60), ); }
Step 2.3: Verify OTP & Finalize Registration
On the OTP screen, when the user enters their code, verify it and mark their phone as verified:
Future<void> verifyOtp(String otpCode, String userId) async { final auth = FirebaseAuth.instance; final firestore = FirebaseFirestore.instance; try { final credential = PhoneAuthProvider.credential( verificationId: verificationId, smsCode: otpCode, ); // Verify the OTP await auth.signInWithCredential(credential); // Update Firestore to mark phone as verified await firestore.collection('users').doc(userId).update({ 'phoneVerified': true, }); // Optional: Link the phone number to the user's auth account await auth.currentUser!.updatePhoneNumber(credential); // Registration complete! Navigate to login or home screen ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Phone verified successfully!'))); Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => LoginScreen())); } on FirebaseAuthException catch (e) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.message!))); } }
Update your existing login logic to check if the user's phone is verified before granting access:
Future<void> login(String email, String password) async { final auth = FirebaseAuth.instance; final firestore = FirebaseFirestore.instance; try { final userCredential = await auth.signInWithEmailAndPassword( email: email, password: password, ); final userId = userCredential.user!.uid; // Check phone verification status from Firestore final userDoc = await firestore.collection('users').doc(userId).get(); final isPhoneVerified = userDoc.data()?['phoneVerified'] ?? false; if (isPhoneVerified) { // Allow access to home screen Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => HomeScreen())); } else { // Log out and prompt verification await auth.signOut(); ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Please verify your phone number first!'))); // Navigate back to OTP verification to re-send code Navigator.push( context, MaterialPageRoute( builder: (_) => OtpVerificationScreen(userId: userId, phone: userDoc.data()!['phoneNumber']) ) ); } } on FirebaseAuthException catch (e) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.message!))); } }
- Phone Number Format: Always enforce country codes to avoid verification failures.
- Firestore Security Rules: Restrict user document access to only the owner:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /users/{userId} { allow read, write: if request.auth != null && request.auth.uid == userId; } } } - Error Handling: Cover all edge cases like expired OTPs, invalid phone numbers, and too many requests to keep users informed.
- State Management: For larger apps, use Provider/Riverpod/Bloc instead of global variables to pass verification IDs and user IDs between screens.
内容的提问来源于stack exchange,提问作者BiNeesH




