Quay lại danh sách bài viết

Flutter Firebase Authentication

30 tháng 11, 2025
admin
Flutter Firebase Authentication
Flutter Firebase Authentication ## Hướng dẫn tích hợp Firebase Authentication vào ứng dụng Flutter để quản lý người dùng ![Flutter Firebase Authentication](./assets/flutter-firebase-auth.jpg) Firebase Authentication là một giải pháp xác thực người dùng mạnh mẽ và an toàn được phát triển bởi Google. Nó cung cấp các phương thức xác thực phổ biến như email/mật khẩu, xác thực qua mạng xã hội (Google, Facebook, Twitter,...), xác thực qua số điện thoại và nhiều phương thức khác. Trong bài viết này, chúng ta sẽ tìm hiểu cách tích hợp Firebase Authentication vào ứng dụng Flutter để xây dựng hệ thống quản lý người dùng hoàn chỉnh. ## Mục lục 1. [Thiết lập dự án Firebase](#thiết-lập-dự-án-firebase) 2. [Cài đặt các gói phụ thuộc cần thiết](#cài-đặt-các-gói-phụ-thuộc-cần-thiết) 3. [Cấu hình Firebase cho ứng dụng Flutter](#cấu-hình-firebase-cho-ứng-dụng-flutter) 4. [Xây dựng các màn hình xác thực](#xây-dựng-các-màn-hình-xác-thực) 5. [Xác thực với Email và Mật khẩu](#xác-thực-với-email-và-mật-khẩu) 6. [Xác thực với Google](#xác-thực-với-google) 7. [Xác thực với Facebook](#xác-thực-với-facebook) 8. [Xác thực với Số điện thoại](#xác-thực-với-số-điện-thoại) 9. [Quản lý trạng thái người dùng](#quản-lý-trạng-thái-người-dùng) 10. [Phân quyền và Bảo mật](#phân-quyền-và-bảo-mật) 11. [Các kỹ thuật nâng cao](#các-kỹ-thuật-nâng-cao) 12. [Xử lý lỗi](#xử-lý-lỗi) 13. [Kết luận](#kết-luận) ## 1. Thiết lập dự án Firebase ### Bước 1: Tạo dự án Firebase 1. Truy cập [Firebase Console](https://console.firebase.google.com/) 2. Nhấp vào "Thêm dự án" (hoặc "Add project") 3. Nhập tên dự án, ví dụ: "Flutter Auth Demo" 4. Tùy chọn bật Google Analytics cho dự án (khuyến nghị) 5. Nhấp "Tiếp tục" và làm theo các bước để hoàn thành quá trình tạo dự án ### Bước 2: Bật Firebase Authentication 1. Trong Firebase Console, chọn dự án vừa tạo 2. Từ menu bên trái, chọn "Authentication" 3. Nhấp vào tab "Sign-in method" 4. Bật các phương thức xác thực mà bạn muốn sử dụng (ví dụ: Email/Password, Google, Facebook, Phone) 5. Cấu hình thêm các thông tin cần thiết cho từng phương thức ![Flutter Firebase Authentication](./assets/flutter-firebase-auth.jpg) ## 2. Cài đặt các gói phụ thuộc cần thiết Mở file `pubspec.yaml` của dự án Flutter và thêm các gói sau: ```yaml dependencies: flutter: sdk: flutter firebase_core: ^2.13.0 firebase_auth: ^4.6.1 google_sign_in: ^6.1.0 flutter_facebook_auth: ^5.0.11 provider: ^6.0.5 ``` Chạy lệnh sau để cài đặt các gói: ```bash flutter pub get ``` ## 3. Cấu hình Firebase cho ứng dụng Flutter ### Cấu hình cho Android #### Bước 1: Đăng ký ứng dụng Android 1. Trong Firebase Console, chọn dự án của bạn 2. Nhấp vào biểu tượng Android để thêm ứng dụng Android 3. Nhập ID gói của ứng dụng Android (ví dụ: `com.example.flutter_auth_demo`) 4. (Tùy chọn) Nhập nickname cho ứng dụng 5. Đăng ký ứng dụng #### Bước 2: Tải xuống file cấu hình 1. Tải xuống file `google-services.json` 2. Đặt file này vào thư mục `android/app` của dự án Flutter #### Bước 3: Cập nhật Gradle Mở file `android/build.gradle` và thêm: ```gradle buildscript { dependencies { // ... other dependencies classpath 'com.google.gms:google-services:4.3.15' } } ``` Mở file `android/app/build.gradle` và thêm: ```gradle // Bottom of the file apply plugin: 'com.google.gms.google-services' ``` ### Cấu hình cho iOS #### Bước 1: Đăng ký ứng dụng iOS 1. Trong Firebase Console, chọn dự án của bạn 2. Nhấp vào biểu tượng iOS để thêm ứng dụng iOS 3. Nhập ID gói của ứng dụng iOS (ví dụ: `com.example.flutterAuthDemo`) 4. (Tùy chọn) Nhập nickname cho ứng dụng 5. Đăng ký ứng dụng #### Bước 2: Tải xuống file cấu hình 1. Tải xuống file `GoogleService-Info.plist` 2. Sử dụng Xcode, thêm file này vào thư mục Runner của dự án (đảm bảo chọn "Copy items if needed") #### Bước 3: Cập nhật Podfile Mở file `ios/Podfile` và thêm: ```ruby platform :ios, '12.0' ``` ## 4. Xây dựng các màn hình xác thực Chúng ta sẽ xây dựng các màn hình xác thực cơ bản cho ứng dụng: ### Màn hình đăng nhập (login_screen.dart) ```dart import 'package:flutter/material.dart'; import 'package:firebase_auth/firebase_auth.dart'; import '../services/auth_service.dart'; class LoginScreen extends StatefulWidget { @override _LoginScreenState createState() => _LoginScreenState(); } class _LoginScreenState extends State<LoginScreen> { final AuthService _authService = AuthService(); final _formKey = GlobalKey<FormState>(); String _email = ''; String _password = ''; String _error = ''; bool _isLoading = false; void _signInWithEmailAndPassword() async { if (_formKey.currentState!.validate()) { setState(() { _isLoading = true; _error = ''; }); try { await _authService.signInWithEmailAndPassword(_email, _password); // Đăng nhập thành công, NavigationService sẽ điều hướng người dùng } catch (e) { setState(() { _error = _handleFirebaseAuthError(e); _isLoading = false; }); } } } String _handleFirebaseAuthError(dynamic e) { if (e is FirebaseAuthException) { switch (e.code) { case 'user-not-found': return 'Không tìm thấy tài khoản với email này.'; case 'wrong-password': return 'Sai mật khẩu.'; case 'invalid-email': return 'Email không hợp lệ.'; case 'user-disabled': return 'Tài khoản đã bị vô hiệu hóa.'; default: return 'Đăng nhập thất bại: ${e.message}'; } } return 'Đã xảy ra lỗi không xác định.'; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Đăng nhập'), ), body: Center( child: SingleChildScrollView( padding: EdgeInsets.all(16.0), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Image.asset( 'assets/images/logo.png', height: 100, ), SizedBox(height: 48.0), TextFormField( decoration: InputDecoration( labelText: 'Email', border: OutlineInputBorder(), prefixIcon: Icon(Icons.email), ), keyboardType: TextInputType.emailAddress, validator: (value) { if (value == null || value.isEmpty) { return 'Vui lòng nhập email'; } return null; }, onChanged: (value) { _email = value.trim(); }, ), SizedBox(height: 16.0), TextFormField( decoration: InputDecoration( labelText: 'Mật khẩu', border: OutlineInputBorder(), prefixIcon: Icon(Icons.lock), ), obscureText: true, validator: (value) { if (value == null || value.isEmpty) { return 'Vui lòng nhập mật khẩu'; } return null; }, onChanged: (value) { _password = value; }, ), SizedBox(height: 24.0), if (_error.isNotEmpty) Padding( padding: EdgeInsets.only(bottom: 16.0), child: Text( _error, style: TextStyle(color: Colors.red), textAlign: TextAlign.center, ), ), ElevatedButton( onPressed: _isLoading ? null : _signInWithEmailAndPassword, child: _isLoading ? CircularProgressIndicator(color: Colors.white) : Text('ĐĂNG NHẬP'), style: ElevatedButton.styleFrom( padding: EdgeInsets.symmetric(vertical: 16.0), ), ), SizedBox(height: 16.0), OutlinedButton.icon( icon: Image.asset( 'assets/images/google_logo.png', height: 24.0, ), label: Text('Đăng nhập với Google'), onPressed: _isLoading ? null : () async { setState(() { _isLoading = true; _error = ''; }); try { await _authService.signInWithGoogle(); // Đăng nhập thành công } catch (e) { setState(() { _error = 'Đăng nhập Google thất bại: $e'; _isLoading = false; }); } }, style: OutlinedButton.styleFrom( padding: EdgeInsets.symmetric(vertical: 12.0), ), ), SizedBox(height: 16.0), TextButton( child: Text('Chưa có tài khoản? Đăng ký ngay'), onPressed: () { Navigator.pushNamed(context, '/register'); }, ), ], ), ), ), ), ); } } ``` ### Màn hình đăng ký (register_screen.dart) ```dart import 'package:flutter/material.dart'; import 'package:firebase_auth/firebase_auth.dart'; import '../services/auth_service.dart'; class RegisterScreen extends StatefulWidget { @override _RegisterScreenState createState() => _RegisterScreenState(); } class _RegisterScreenState extends State<RegisterScreen> { final AuthService _authService = AuthService(); final _formKey = GlobalKey<FormState>(); String _email = ''; String _password = ''; String _confirmPassword = ''; String _error = ''; bool _isLoading = false; void _registerWithEmailAndPassword() async { if (_formKey.currentState!.validate()) { setState(() { _isLoading = true; _error = ''; }); try { await _authService.registerWithEmailAndPassword(_email, _password); // Đăng ký thành công, NavigationService sẽ điều hướng người dùng } catch (e) { setState(() { _error = _handleFirebaseAuthError(e); _isLoading = false; }); } } } String _handleFirebaseAuthError(dynamic e) { if (e is FirebaseAuthException) { switch (e.code) { case 'email-already-in-use': return 'Email này đã được sử dụng.'; case 'invalid-email': return 'Email không hợp lệ.'; case 'operation-not-allowed': return 'Đăng ký với email và mật khẩu chưa được bật.'; case 'weak-password': return 'Mật khẩu quá yếu.'; default: return 'Đăng ký thất bại: ${e.message}'; } } return 'Đã xảy ra lỗi không xác định.'; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Đăng ký'), ), body: Center( child: SingleChildScrollView( padding: EdgeInsets.all(16.0), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Image.asset( 'assets/images/logo.png', height: 100, ), SizedBox(height: 48.0), TextFormField( decoration: InputDecoration( labelText: 'Email', border: OutlineInputBorder(), prefixIcon: Icon(Icons.email), ), keyboardType: TextInputType.emailAddress, validator: (value) { if (value == null || value.isEmpty) { return 'Vui lòng nhập email'; } return null; }, onChanged: (value) { _email = value.trim(); }, ), SizedBox(height: 16.0), TextFormField( decoration: InputDecoration( labelText: 'Mật khẩu', border: OutlineInputBorder(), prefixIcon: Icon(Icons.lock), ), obscureText: true, validator: (value) { if (value == null || value.isEmpty) { return 'Vui lòng nhập mật khẩu'; } if (value.length < 6) { return 'Mật khẩu phải có ít nhất 6 ký tự'; } return null; }, onChanged: (value) { _password = value; }, ), SizedBox(height: 16.0), TextFormField( decoration: InputDecoration( labelText: 'Xác nhận mật khẩu', border: OutlineInputBorder(), prefixIcon: Icon(Icons.lock_outline), ), obscureText: true, validator: (value) { if (value == null || value.isEmpty) { return 'Vui lòng xác nhận mật khẩu'; } if (value != _password) { return 'Mật khẩu không khớp'; } return null; }, onChanged: (value) { _confirmPassword = value; }, ), SizedBox(height: 24.0), if (_error.isNotEmpty) Padding( padding: EdgeInsets.only(bottom: 16.0), child: Text( _error, style: TextStyle(color: Colors.red), textAlign: TextAlign.center, ), ), ElevatedButton( onPressed: _isLoading ? null : _registerWithEmailAndPassword, child: _isLoading ? CircularProgressIndicator(color: Colors.white) : Text('ĐĂNG KÝ'), style: ElevatedButton.styleFrom( padding: EdgeInsets.symmetric(vertical: 16.0), ), ), SizedBox(height: 16.0), TextButton( child: Text('Đã có tài khoản? Đăng nhập ngay'), onPressed: () { Navigator.pop(context); }, ), ], ), ), ), ), ); } } ``` ## 5. Xác thực với Email và Mật khẩu Tạo một service class để xử lý xác thực (`lib/services/auth_service.dart`): ```dart import 'package:firebase_auth/firebase_auth.dart'; class AuthService { final FirebaseAuth _auth = FirebaseAuth.instance; // Lấy thông tin người dùng hiện tại User? get currentUser => _auth.currentUser; // Stream thông tin người dùng (để lắng nghe trạng thái đăng nhập) Stream<User?> get authStateChanges => _auth.authStateChanges(); // Đăng nhập với email và mật khẩu Future<UserCredential> signInWithEmailAndPassword(String email, String password) async { try { return await _auth.signInWithEmailAndPassword( email: email, password: password, ); } catch (e) { rethrow; } } // Đăng ký với email và mật khẩu Future<UserCredential> registerWithEmailAndPassword(String email, String password) async { try { return await _auth.createUserWithEmailAndPassword( email: email, password: password, ); } catch (e) { rethrow; } } // Đăng xuất Future<void> signOut() async { await _auth.signOut(); } // Quên mật khẩu Future<void> resetPassword(String email) async { await _auth.sendPasswordResetEmail(email: email); } } ``` ## 6. Xác thực với Google Bổ sung phương thức đăng nhập với Google vào AuthService: ```dart import 'package:firebase_auth/firebase_auth.dart'; import 'package:google_sign_in/google_sign_in.dart'; class AuthService { // ... (Các phương thức đã có) // Đăng nhập với Google Future<UserCredential> signInWithGoogle() async { try { // Bắt đầu quá trình đăng nhập Google final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn(); // Nếu người dùng hủy đăng nhập if (googleUser == null) { throw FirebaseAuthException( code: 'sign_in_canceled', message: 'Đăng nhập bị hủy bởi người dùng', ); } // Lấy thông tin xác thực từ request final GoogleSignInAuthentication googleAuth = await googleUser.authentication; // Tạo credential mới final credential = GoogleAuthProvider.credential( accessToken: googleAuth.accessToken, idToken: googleAuth.idToken, ); // Đăng nhập vào Firebase với credential return await FirebaseAuth.instance.signInWithCredential(credential); } catch (e) { rethrow; } } } ``` ### Cấu hình Google Sign-In cho Android Thêm vào file `android/app/build.gradle`: ```gradle android { defaultConfig { // ... // Thêm dòng này multiDexEnabled true } } ``` ### Cấu hình Google Sign-In cho iOS Thêm vào file `ios/Runner/Info.plist`: ```xml <key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleTypeRole</key> <string>Editor</string> <key>CFBundleURLSchemes</key> <array> <!-- Thay YOUR_REVERSED_CLIENT_ID bằng giá trị từ GoogleService-Info.plist --> <string>YOUR_REVERSED_CLIENT_ID</string> </array> </dict> </array> ``` ## 7. Xác thực với Facebook Bổ sung phương thức đăng nhập với Facebook vào AuthService: ```dart import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter_facebook_auth/flutter_facebook_auth.dart'; class AuthService { // ... (Các phương thức đã có) // Đăng nhập với Facebook Future<UserCredential> signInWithFacebook() async { try { // Bắt đầu quá trình đăng nhập Facebook final LoginResult result = await FacebookAuth.instance.login(); // Kiểm tra kết quả if (result.status != LoginStatus.success) { throw FirebaseAuthException( code: 'facebook_login_failed', message: 'Đăng nhập Facebook thất bại: ${result.message}', ); } // Tạo credential từ token final OAuthCredential credential = FacebookAuthProvider.credential(result.accessToken!.token); // Đăng nhập vào Firebase với credential return await FirebaseAuth.instance.signInWithCredential(credential); } catch (e) { rethrow; } } } ``` ### Cấu hình Facebook Login cho Android Thêm vào file `android/app/src/main/res/values/strings.xml` (tạo nếu chưa có): ```xml <?xml version="1.0" encoding="utf-8"?> <resources> <string name="facebook_app_id">YOUR_FACEBOOK_APP_ID</string> <string name="fb_login_protocol_scheme">fbYOUR_FACEBOOK_APP_ID</string> <string name="facebook_client_token">YOUR_FACEBOOK_CLIENT_TOKEN</string> </resources> ``` Thêm vào file `android/app/src/main/AndroidManifest.xml`: ```xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.your_app"> <!-- ... --> <application> <!-- ... --> <meta-data android:name="com.facebook.sdk.ApplicationId" android:value="@string/facebook_app_id"/> <meta-data android:name="com.facebook.sdk.ClientToken" android:value="@string/facebook_client_token"/> <activity android:name="com.facebook.FacebookActivity" android:configChanges="keyboard|keyboardHidden|screenLayout|screenSize|orientation" android:label="@string/app_name" /> <activity android:name="com.facebook.CustomTabActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="@string/fb_login_protocol_scheme" /> </intent-filter> </activity> </application> </manifest> ``` ### Cấu hình Facebook Login cho iOS Thêm vào file `ios/Runner/Info.plist`: ```xml <key>CFBundleURLTypes</key> <array> <!-- ... Các URL types khác --> <dict> <key>CFBundleURLSchemes</key> <array> <string>fbYOUR_FACEBOOK_APP_ID</string> </array> </dict> </array> <key>FacebookAppID</key> <string>YOUR_FACEBOOK_APP_ID</string> <key>FacebookClientToken</key> <string>YOUR_FACEBOOK_CLIENT_TOKEN</string> <key>FacebookDisplayName</key> <string>YOUR_APP_NAME</string> <key>LSApplicationQueriesSchemes</key> <array> <string>fbapi</string> <string>fb-messenger-share-api</string> <string>fbauth2</string> <string>fbshareextension</string> </array> ``` ## 8. Xác thực với Số điện thoại Bổ sung phương thức đăng nhập với số điện thoại vào AuthService: ```dart class AuthService { // ... (Các phương thức đã có) // Xác thực số điện thoại - Bước 1: Gửi OTP Future<void> verifyPhoneNumber({ required String phoneNumber, required Function(PhoneAuthCredential) verificationCompleted, required Function(FirebaseAuthException) verificationFailed, required Function(String, int?) codeSent, required Function(String) codeAutoRetrievalTimeout, }) async { await _auth.verifyPhoneNumber( phoneNumber: phoneNumber, verificationCompleted: verificationCompleted, verificationFailed: verificationFailed, codeSent: codeSent, codeAutoRetrievalTimeout: codeAutoRetrievalTimeout, timeout: Duration(seconds: 60), ); } // Xác thực số điện thoại - Bước 2: Xác nhận OTP Future<UserCredential> signInWithPhoneCredential(PhoneAuthCredential credential) async { try { return await _auth.signInWithCredential(credential); } catch (e) { rethrow; } } } ``` Tạo màn hình xác thực số điện thoại (`phone_auth_screen.dart`): ```dart import 'package:flutter/material.dart'; import 'package:firebase_auth/firebase_auth.dart'; import '../services/auth_service.dart'; class PhoneAuthScreen extends StatefulWidget { @override _PhoneAuthScreenState createState() => _PhoneAuthScreenState(); } class _PhoneAuthScreenState extends State<PhoneAuthScreen> { final AuthService _authService = AuthService(); final _formKey = GlobalKey<FormState>(); String _phoneNumber = ''; String _smsCode = ''; String _verificationId = ''; String _error = ''; bool _isLoading = false; bool _codeSent = false; void _verifyPhoneNumber() async { if (_formKey.currentState!.validate()) { setState(() { _isLoading = true; _error = ''; }); try { await _authService.verifyPhoneNumber( phoneNumber: _phoneNumber, verificationCompleted: (PhoneAuthCredential credential) async { // Tự động xác thực (Android) try { await _authService.signInWithPhoneCredential(credential); // Đăng nhập thành công } catch (e) { setState(() { _error = 'Xác thực thất bại: $e'; _isLoading = false; }); } }, verificationFailed: (FirebaseAuthException e) { setState(() { _error = 'Xác thực thất bại: ${e.message}'; _isLoading = false; }); }, codeSent: (String verificationId, int? resendToken) { setState(() { _verificationId = verificationId; _codeSent = true; _isLoading = false; }); }, codeAutoRetrievalTimeout: (String verificationId) { _verificationId = verificationId; }, ); } catch (e) { setState(() { _error = 'Lỗi gửi mã: $e'; _isLoading = false; }); } } } void _signInWithSmsCode() async { if (_formKey.currentState!.validate()) { setState(() { _isLoading = true; _error = ''; }); try { PhoneAuthCredential credential = PhoneAuthProvider.credential( verificationId: _verificationId, smsCode: _smsCode, ); await _authService.signInWithPhoneCredential(credential); // Đăng nhập thành công } catch (e) { setState(() { _error = 'Xác thực thất bại: $e'; _isLoading = false; }); } } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Đăng nhập với Số điện thoại'), ), body: Center( child: SingleChildScrollView( padding: EdgeInsets.all(16.0), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Image.asset( 'assets/images/logo.png', height: 100, ), SizedBox(height: 48.0), if (!_codeSent) ...[ TextFormField( decoration: InputDecoration( labelText: 'Số điện thoại (định dạng: +84...)', border: OutlineInputBorder(), prefixIcon: Icon(Icons.phone), ), keyboardType: TextInputType.phone, validator: (value) { if (value == null || value.isEmpty) { return 'Vui lòng nhập số điện thoại'; } return null; }, onChanged: (value) { _phoneNumber = value.trim(); }, ), ] else ...[ TextFormField( decoration: InputDecoration( labelText: 'Mã xác thực (OTP)', border: OutlineInputBorder(), prefixIcon: Icon(Icons.sms), ), keyboardType: TextInputType.number, validator: (value) { if (value == null || value.isEmpty) { return 'Vui lòng nhập mã xác thực'; } return null; }, onChanged: (value) { _smsCode = value.trim(); }, ), ], SizedBox(height: 24.0), if (_error.isNotEmpty) Padding( padding: EdgeInsets.only(bottom: 16.0), child: Text( _error, style: TextStyle(color: Colors.red), textAlign: TextAlign.center, ), ), ElevatedButton( onPressed: _isLoading ? null : (_codeSent ? _signInWithSmsCode : _verifyPhoneNumber), child: _isLoading ? CircularProgressIndicator(color: Colors.white) : Text(_codeSent ? 'XÁC THỰC' : 'GỬI MÃ XÁC THỰC'), style: ElevatedButton.styleFrom( padding: EdgeInsets.symmetric(vertical: 16.0), ), ), if (_codeSent) ...[ SizedBox(height: 16.0), TextButton( child: Text('Gửi lại mã'), onPressed: _isLoading ? null : _verifyPhoneNumber, ), ], ], ), ), ), ), ); } } ``` ## 9. Quản lý trạng thái người dùng Sử dụng Provider để quản lý trạng thái người dùng trong ứng dụng (`lib/providers/auth_provider.dart`): ```dart import 'package:flutter/material.dart'; import 'package:firebase_auth/firebase_auth.dart'; import '../services/auth_service.dart'; class AuthProvider with ChangeNotifier { final AuthService _authService = AuthService(); User? _user; bool _isLoading = false; AuthProvider() { _init(); } void _init() { _authService.authStateChanges.listen((User? user) { _user = user; notifyListeners(); }); } User? get user => _user; bool get isAuthenticated => _user != null; bool get isLoading => _isLoading; void setLoading(bool value) { _isLoading = value; notifyListeners(); } Future<void> signInWithEmailAndPassword(String email, String password) async { try { setLoading(true); await _authService.signInWithEmailAndPassword(email, password); } finally { setLoading(false); } } Future<void> registerWithEmailAndPassword(String email, String password) async { try { setLoading(true); await _authService.registerWithEmailAndPassword(email, password); } finally { setLoading(false); } } Future<void> signInWithGoogle() async { try { setLoading(true); await _authService.signInWithGoogle(); } finally { setLoading(false); } } Future<void> signInWithFacebook() async { try { setLoading(true); await _authService.signInWithFacebook(); } finally { setLoading(false); } } Future<void> signOut() async { try { setLoading(true); await _authService.signOut(); } finally { setLoading(false); } } } ``` Cập nhật `main.dart` để sử dụng Provider: ```dart import 'package:flutter/material.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:provider/provider.dart'; import 'providers/auth_provider.dart'; import 'screens/login_screen.dart'; import 'screens/register_screen.dart'; import 'screens/home_screen.dart'; import 'screens/phone_auth_screen.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(); runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => AuthProvider()), ], child: Consumer<AuthProvider>( builder: (context, authProvider, _) { return MaterialApp( title: 'Flutter Firebase Auth', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), initialRoute: authProvider.isAuthenticated ? '/home' : '/login', routes: { '/login': (context) => LoginScreen(), '/register': (context) => RegisterScreen(), '/home': (context) => HomeScreen(), '/phone': (context) => PhoneAuthScreen(), }, ); }, ), ); } } ``` ## 10. Phân quyền và Bảo mật Để bảo vệ các tuyến đường chỉ cho người dùng đã xác thực, tạo một `auth_guard.dart`: ```dart import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../providers/auth_provider.dart'; class AuthGuard extends StatelessWidget { final Widget child; final String redirectRoute; const AuthGuard({ Key? key, required this.child, this.redirectRoute = '/login', }) : super(key: key); @override Widget build(BuildContext context) { return Consumer<AuthProvider>( builder: (context, authProvider, _) { if (authProvider.isAuthenticated) { return child; } else { // Điều hướng đến trang đăng nhập sau khi render WidgetsBinding.instance.addPostFrameCallback((_) { Navigator.of(context).pushReplacementNamed(redirectRoute); }); // Trả về một widget đơn giản trong khi chờ điều hướng return Scaffold( body: Center( child: CircularProgressIndicator(), ), ); } }, ); } } ``` Sử dụng trong tuyến đường cần bảo vệ: ```dart // Trong home_screen.dart @override Widget build(BuildContext context) { return AuthGuard( child: Scaffold( appBar: AppBar( title: Text('Trang chủ'), actions: [ IconButton( icon: Icon(Icons.exit_to_app), onPressed: () { Provider.of<AuthProvider>(context, listen: false).signOut(); }, ), ], ), body: Center( child: Text('Đã đăng nhập thành công!'), ), ), ); } ``` ## 11. Các kỹ thuật nâng cao ### 11.1. Quản lý thông tin người dùng với Firestore Tạo một service để quản lý thông tin người dùng trong Firestore (`user_service.dart`): ```dart import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; class UserService { final FirebaseFirestore _firestore = FirebaseFirestore.instance; final FirebaseAuth _auth = FirebaseAuth.instance; Future<void> createUserProfile(User user, {Map<String, dynamic>? additionalData}) async { final Map<String, dynamic> userData = { 'email': user.email, 'displayName': user.displayName, 'photoURL': user.photoURL, 'phoneNumber': user.phoneNumber, 'lastLogin': FieldValue.serverTimestamp(), 'createdAt': FieldValue.serverTimestamp(), ...?additionalData, }; await _firestore.collection('users').doc(user.uid).set(userData, SetOptions(merge: true)); } Future<DocumentSnapshot> getUserProfile(String userId) async { return await _firestore.collection('users').doc(userId).get(); } Future<void> updateUserProfile(String userId, Map<String, dynamic> data) async { await _firestore.collection('users').doc(userId).update(data); } Stream<DocumentSnapshot> getUserProfileStream(String userId) { return _firestore.collection('users').doc(userId).snapshots(); } Future<void> updateCurrentUserProfile(Map<String, dynamic> data) async { final user = _auth.currentUser; if (user != null) { await updateUserProfile(user.uid, data); } else { throw Exception('Không có người dùng đang đăng nhập'); } } } ``` ### 11.2. Xử lý phiên đăng nhập Bổ sung chức năng xác thực lại sau một khoảng thời gian nhất định: ```dart class AuthService { // ... (Các phương thức đã có) // Xác thực lại người dùng hiện tại Future<void> reauthenticateUser(String password) async { try { User? user = _auth.currentUser; if (user == null || user.email == null) { throw Exception('Không thể xác thực lại. Không có thông tin người dùng.'); } AuthCredential credential = EmailAuthProvider.credential( email: user.email!, password: password, ); await user.reauthenticateWithCredential(credential); } catch (e) { rethrow; } } // Thay đổi mật khẩu (yêu cầu xác thực lại trước) Future<void> changePassword(String newPassword) async { try { User? user = _auth.currentUser; if (user == null) { throw Exception('Không có người dùng đang đăng nhập'); } await user.updatePassword(newPassword); } catch (e) { rethrow; } } // Kiểm tra thời gian đăng nhập gần nhất Future<bool> isSessionValid(int maxAgeMinutes) async { try { User? user = _auth.currentUser; if (user == null) { return false; } // Làm mới token người dùng để cập nhật metadata await user.getIdToken(true); // Lấy metadata final metadata = await user.metadata; final lastSignInTime = metadata.lastSignInTime; if (lastSignInTime == null) { return false; } final now = DateTime.now(); final diff = now.difference(lastSignInTime).inMinutes; return diff <= maxAgeMinutes; } catch (e) { return false; } } } ``` ### 11.3. Xác thực hai yếu tố (2FA) ```dart class AuthService { // ... (Các phương thức đã có) // Bật xác thực hai yếu tố Future<String> enableTwoFactorAuth() async { try { User? user = _auth.currentUser; if (user == null) { throw Exception('Không có người dùng đang đăng nhập'); } // Tạo URI để cấu hình ứng dụng xác thực (như Google Authenticator) final appName = 'YourAppName'; final email = user.email ?? user.uid; final secret = _generateSecret(); // Implement this method final uri = 'otpauth://totp/$appName:$email?secret=$secret&issuer=$appName'; // Lưu secret vào Firestore (User document) await FirebaseFirestore.instance.collection('users').doc(user.uid).update({ 'twoFactorEnabled': true, 'twoFactorSecret': secret, }); return uri; } catch (e) { rethrow; } } // Xác minh mã OTP Future<bool> verifyOtp(String otp) async { try { User? user = _auth.currentUser; if (user == null) { throw Exception('Không có người dùng đang đăng nhập'); } // Lấy secret từ Firestore final doc = await FirebaseFirestore.instance.collection('users').doc(user.uid).get(); final secret = doc.data()?['twoFactorSecret']; if (secret == null) { throw Exception('Xác thực hai yếu tố chưa được bật'); } // Xác minh OTP return _verifyOtp(secret, otp); // Implement this method } catch (e) { rethrow; } } } ``` ## 12. Xử lý lỗi Tạo một helper class để xử lý các lỗi liên quan đến Firebase Auth (`auth_error_handler.dart`): ```dart class AuthErrorHandler { static String handleAuthError(dynamic error) { if (error is FirebaseAuthException) { switch (error.code) { // Email/Password Auth Errors case 'email-already-in-use': return 'Email này đã được sử dụng bởi một tài khoản khác.'; case 'invalid-email': return 'Email không hợp lệ.'; case 'user-disabled': return 'Tài khoản này đã bị vô hiệu hóa.'; case 'user-not-found': return 'Không tìm thấy tài khoản với email này.'; case 'wrong-password': return 'Sai mật khẩu.'; case 'weak-password': return 'Mật khẩu quá yếu.'; case 'operation-not-allowed': return 'Phương thức đăng nhập này không được cho phép.'; // Phone Auth Errors case 'invalid-phone-number': return 'Số điện thoại không hợp lệ.'; case 'missing-phone-number': return 'Vui lòng nhập số điện thoại.'; case 'quota-exceeded': return 'Đã vượt quá giới hạn SMS, vui lòng thử lại sau.'; case 'session-expired': return 'Phiên xác thực đã hết hạn, vui lòng gửi lại mã.'; case 'invalid-verification-code': return 'Mã xác thực không hợp lệ.'; // Google/Facebook Auth Errors case 'account-exists-with-different-credential': return 'Đã tồn tại tài khoản với email này nhưng sử dụng phương thức đăng nhập khác.'; case 'invalid-credential': return 'Thông tin xác thực không hợp lệ.'; case 'operation-not-supported-in-this-environment': return 'Phương thức xác thực này không được hỗ trợ trên nền tảng hiện tại.'; case 'popup-blocked': return 'Cửa sổ đăng nhập bị chặn. Vui lòng cho phép cửa sổ bật lên và thử lại.'; case 'popup-closed-by-user': return 'Cửa sổ đăng nhập đã bị đóng trước khi hoàn tất quá trình xác thực.'; // General Auth Errors case 'network-request-failed': return 'Lỗi kết nối mạng. Vui lòng kiểm tra kết nối của bạn và thử lại.'; case 'too-many-requests': return 'Quá nhiều yêu cầu không thành công. Vui lòng thử lại sau.'; case 'internal-error': return 'Đã xảy ra lỗi nội bộ. Vui lòng thử lại sau.'; default: return 'Đã xảy ra lỗi: ${error.message}'; } } return 'Đã xảy ra lỗi không xác định.'; } } ``` ## 13. Kết luận ![Flutter Firebase Authentication](./assets/flutter-firebase-auth.jpg) Firebase Authentication cung cấp một giải pháp xác thực mạnh mẽ và linh hoạt cho ứng dụng Flutter của bạn. Việc tích hợp Firebase Authentication giúp bạn: 1. **Tiết kiệm thời gian phát triển**: Không cần xây dựng hệ thống xác thực từ đầu 2. **Bảo mật cao**: Firebase cung cấp các cơ chế bảo mật tiên tiến 3. **Hỗ trợ nhiều phương thức xác thực**: Email/mật khẩu, Google, Facebook, số điện thoại,... 4. **Dễ dàng mở rộng**: Liên kết với Firebase Firestore để lưu trữ thông tin người dùng 5. **Quản lý người dùng đơn giản**: Giao diện Firebase Console thân thiện Bằng cách tích hợp Firebase Authentication vào ứng dụng Flutter, bạn đã xây dựng một hệ thống xác thực toàn diện, bảo mật, và dễ dàng mở rộng. Hệ thống này có thể đáp ứng nhu cầu của các ứng dụng từ đơn giản đến phức tạp, giúp bạn tập trung vào phát triển các tính năng cốt lõi của ứng dụng. Hãy luôn cập nhật các gói Firebase mới nhất để đảm bảo ứng dụng của bạn luôn nhận được các cải tiến và bản vá bảo mật mới nhất. **Tài nguyên học tập thêm**: - [Firebase Authentication Documentation](https://firebase.google.com/docs/auth) - [Flutter Firebase Authentication Codelab](https://firebase.google.com/codelabs/firebase-auth-in-flutter-apps) - [FlutterFire Documentation](https://firebase.flutter.dev/docs/auth/overview) Chúc bạn thành công trong việc xây dựng hệ thống xác thực cho ứng dụng Flutter!
flutter
firebase
authentication
google-sign-in
facebook-login
Chia sẻ:

Bài viết liên quan

Supabase - Nền Tảng Backend-as-a-Service Hiện Đại

Supabase - Nền Tảng Backend-as-a-Service Hiện Đại <div className="search-container"> <input type="text" placeholder="Tìm kiếm trong bà...

Cách chạy Flutter trên Android/iOS/Web

Cách chạy Flutter trên Android/iOS/Web ![Flutter trên các nền tảng](/img/blog/flutter_platforms.svg) Giới thiệu Flutter là một framework phá...

Flutter - Framework Phát Triển Ứng Dụng Đa Nền Tảng

Các ứng dụng thành công được xây dựng bằng Flutter ![Flutter Apps](/img/blog/flutter-apps.svg) Flutter đã trở thành một trong những framework ph...