Flutter & Firebase Auth: Implementing Multi-Provider Login
Modern applications frequently require users to sign in using various methods, enhancing user convenience and accessibility. Implementing multi-provider login can be a complex task, but with Flutter and Firebase Authentication, it becomes significantly more manageable. This article will guide you through building a multi-provider login system in Flutter, leveraging Firebase Auth and the provider package for robust state management.
Why Multi-Provider Login?
Offering multiple login options (e.g., email/password, Google, Facebook) provides several benefits:
- Improved User Experience: Users can choose their preferred login method, often one they already use and trust.
- Wider Reach: Accommodates users who might not want to create a new password or remember another set of credentials.
- Reduced Friction: Social logins often allow for a quicker sign-up and login process.
Prerequisites
- A basic understanding of Flutter development.
- A Firebase project set up with Flutter integration.
- Familiarity with the
providerpackage is beneficial.
Step 1: Flutter Project and Firebase Setup
1.1 Flutter Project Creation
If you don't have a Flutter project, create one:
flutter create flutter_multi_auth
cd flutter_multi_auth
1.2 Add Dependencies
Add the necessary dependencies to your pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
firebase_core: ^2.24.2 # Or the latest version
firebase_auth: ^4.15.2 # Or the latest version
provider: ^6.0.5 # Or the latest version
google_sign_in: ^6.1.6 # Or the latest version for Google Sign-In
flutter_facebook_auth: ^6.1.0 # Or the latest version for Facebook Sign-In
Run flutter pub get to fetch the packages.
1.3 Firebase Project Configuration
Ensure your Firebase project is correctly configured for your Flutter app (Android, iOS, Web). Then, enable the desired sign-in methods in the Firebase console (Authentication > Sign-in method):
- Email/Password: Enable this method.
- Google: Enable this method. Follow the instructions to configure your SHA-1 key for Android and GoogleService-Info.plist for iOS.
- Facebook: Enable this method. You'll need an app created in the Facebook Developer Console. Provide the App ID and App Secret to Firebase. For Android and iOS, also configure your
AndroidManifest.xmlandInfo.plistrespectively as perflutter_facebook_authpackage documentation for URL schemes and meta-data.
Step 2: Defining the Authentication Service
We'll create a dedicated service to encapsulate all Firebase Authentication logic. This promotes clean architecture and testability.
2.1 User Model
A simple model to represent our authenticated user:
// lib/models/app_user.dart
import 'package:firebase_auth/firebase_auth.dart';
class AppUser {
final String? uid;
final String? email;
final String? displayName;
final String? photoURL;
AppUser({this.uid, this.email, this.displayName, this.photoURL});
factory AppUser.fromFirebaseUser(User? user) {
return AppUser(
uid: user?.uid,
email: user?.email,
displayName: user?.displayName,
photoURL: user?.photoURL,
);
}
}
2.2 FirebaseAuthService
This service will handle all interactions with firebase_auth.
// lib/services/auth_service.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
import '../models/app_user.dart';
class AuthService {
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
final GoogleSignIn _googleSignIn = GoogleSignIn();
final FacebookAuth _facebookAuth = FacebookAuth.instance;
// Stream to observe user authentication state
Stream<AppUser?> get user {
return _firebaseAuth.authStateChanges().map(AppUser.fromFirebaseUser);
}
// Sign in with Email and Password
Future<AppUser?> signInWithEmailAndPassword(String email, String password) async {
try {
UserCredential result = await _firebaseAuth.signInWithEmailAndPassword(
email: email,
password: password,
);
return AppUser.fromFirebaseUser(result.user);
} catch (e) {
print(e.toString());
return null;
}
}
// Register with Email and Password
Future<AppUser?> registerWithEmailAndPassword(String email, String password) async {
try {
UserCredential result = await _firebaseAuth.createUserWithEmailAndPassword(
email: email,
password: password,
);
return AppUser.fromFirebaseUser(result.user);
} catch (e) {
print(e.toString());
return null;
}
}
// Sign in with Google
Future<AppUser?> signInWithGoogle() async {
try {
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
if (googleUser == null) return null; // User cancelled the sign-in
final GoogleSignInAuthentication googleAuth = await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
UserCredential result = await _firebaseAuth.signInWithCredential(credential);
return AppUser.fromFirebaseUser(result.user);
} catch (e) {
print(e.toString());
return null;
}
}
// Sign in with Facebook
Future<AppUser?> signInWithFacebook() async {
try {
final LoginResult result = await _facebookAuth.login();
if (result.status == LoginStatus.success) {
final accessToken = result.accessToken;
if (accessToken == null) return null;
final AuthCredential credential = FacebookAuthProvider.credential(accessToken.token);
UserCredential userCredential = await _firebaseAuth.signInWithCredential(credential);
return AppUser.fromFirebaseUser(userCredential.user);
} else if (result.status == LoginStatus.cancelled) {
print('Facebook login cancelled.');
return null;
} else {
print('Facebook login failed: ${result.message}');
return null;
}
} catch (e) {
print(e.toString());
return null;
}
}
// Sign out
Future<void> signOut() async {
try {
await _googleSignIn.signOut();
await _facebookAuth.logOut();
await _firebaseAuth.signOut();
} catch (e) {
print(e.toString());
}
}
}
Step 3: Integrating with Provider for State Management
We'll use the provider package to make our AuthService available throughout the widget tree and to reactively update the UI based on the user's authentication state.
3.1 main.dart
Initialize Firebase and provide the AuthService.
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:provider/provider.dart';
import 'firebase_options.dart'; // Generated by FlutterFire CLI
import 'services/auth_service.dart';
import 'models/app_user.dart';
import 'screens/login_screen.dart';
import 'screens/home_screen.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider<AuthService>(
create: (_) => AuthService(),
),
StreamProvider<AppUser?>(
create: (context) => context.read<AuthService>().user,
initialData: null,
),
],
child: MaterialApp(
title: 'Flutter Multi Auth',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const AuthWrapper(),
),
);
}
}
// Widget to handle navigation based on auth state
class AuthWrapper extends StatelessWidget {
const AuthWrapper({super.key});
@override
Widget build(BuildContext context) {
final AppUser? user = Provider.of<AppUser?>(context);
if (user == null) {
return const LoginScreen();
} else {
return const HomeScreen();
}
}
}
Step 4: Building the User Interface
4.1 Login Screen
This screen will provide input fields for email/password and buttons for social logins.
// lib/screens/login_screen.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../services/auth_service.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final authService = Provider.of<AuthService>(context);
return Scaffold(
appBar: AppBar(title: const Text('Login')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
controller: _emailController,
decoration: const InputDecoration(labelText: 'Email'),
),
TextField(
controller: _passwordController,
decoration: const InputDecoration(labelText: 'Password'),
obscureText: true,
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () async {
final user = await authService.signInWithEmailAndPassword(
_emailController.text,
_passwordController.text,
);
if (user == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Failed to sign in with email/password.')),
);
}
},
child: const Text('Sign In with Email'),
),
TextButton(
onPressed: () async {
final user = await authService.registerWithEmailAndPassword(
_emailController.text,
_passwordController.text,
);
if (user == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Failed to register with email/password.')),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Registration successful!')),
);
}
},
child: const Text('Register with Email'),
),
const SizedBox(height: 20),
ElevatedButton.icon(
icon: Image.asset('assets/google_logo.png', height: 24), // Add a Google logo asset
label: const Text('Sign In with Google'),
onPressed: () async {
final user = await authService.signInWithGoogle();
if (user == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Failed to sign in with Google.')),
);
}
},
),
const SizedBox(height: 10),
ElevatedButton.icon(
icon: Image.asset('assets/facebook_logo.png', height: 24), // Add a Facebook logo asset
label: const Text('Sign In with Facebook'),
onPressed: () async {
final user = await authService.signInWithFacebook();
if (user == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Failed to sign in with Facebook.')),
);
}
},
),
],
),
),
);
}
}
Note: You'll need to add google_logo.png and facebook_logo.png to your assets/ folder and declare the folder in pubspec.yaml.
4.2 Home Screen
A simple screen displayed after successful login, showing user info and a logout button.
// lib/screens/home_screen.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../services/auth_service.dart';
import '../models/app_user.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
final authService = Provider.of<AuthService>(context);
final AppUser? user = Provider.of<AppUser?>(context);
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.logout),
onPressed: () async {
await authService.signOut();
},
),
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Welcome, ${user?.displayName ?? user?.email ?? 'User'}!'),
if (user?.photoURL != null)
Padding(
padding: const EdgeInsets.all(8.0),
child: CircleAvatar(
radius: 50,
backgroundImage: NetworkImage(user!.photoURL!),
),
),
const Text('You are successfully logged in.'),
],
),
),
);
}
}
Conclusion
Implementing a multi-provider login system in Flutter with Firebase Authentication and the provider package significantly streamlines the development process. By abstracting authentication logic into a dedicated service and managing state reactively, you can create a robust, scalable, and user-friendly authentication flow. This setup not only offers flexibility to your users but also maintains a clean and maintainable codebase.