Flutter & Firebase Auth: Implementing Single Sign-On (SSO)
In today's interconnected digital landscape, user experience is paramount. Single Sign-On (SSO) plays a crucial role in enhancing this experience by allowing users to access multiple applications with a single set of credentials. This not only improves convenience but also strengthens security by reducing password fatigue. When building cross-platform applications with Flutter, integrating a robust authentication backend is essential. Firebase Authentication stands out as an excellent choice, offering a managed, scalable, and secure service that natively supports various SSO providers.
This article will guide you through implementing SSO in a Flutter application using Firebase Authentication, focusing on Google Sign-In as a primary example, while also outlining the path for other providers.
The Power of Flutter and Firebase Auth for SSO
- Unified User Management: Firebase Auth provides a single backend for managing all user accounts, regardless of the authentication method they use (Google, Apple, Facebook, Email/Password, Phone Number).
- Multiple Provider Support: It offers out-of-the-box support for popular identity providers, simplifying the integration process for SSO.
- Scalability and Security: Being a Google-managed service, Firebase Auth scales automatically and incorporates robust security features, including multi-factor authentication and fraud detection.
- Cross-Platform Development: Flutter's ability to build native applications for mobile, web, and desktop from a single codebase perfectly complements Firebase's platform-agnostic nature, allowing you to implement SSO once for all your platforms.
Getting Started: Firebase Project Setup
Before diving into Flutter code, you need to set up your Firebase project:
- Create a Firebase Project: Go to the Firebase Console and create a new project.
- Add Flutter App to Firebase: Follow the instructions to add your Flutter app (for Android, iOS, Web, etc.) to your Firebase project. This involves downloading configuration files (
google-services.jsonfor Android,GoogleService-Info.plistfor iOS) and adding them to your Flutter project. - Enable Authentication Providers:
- In the Firebase Console, navigate to "Authentication" -> "Sign-in method".
- Enable the "Google" provider. You might need to configure your app's public-facing name and support email. Firebase will automatically provide a Web SDK configuration, which is crucial for web integration and sometimes for native mobile setup.
- Enable any other providers you plan to support (e.g., Apple, Facebook, Email/Password). Each will have its own setup steps, often requiring registration with the respective developer console (e.g., Apple Developer Program, Facebook Developer Account).
Flutter Project Setup
Add the necessary dependencies to your pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
firebase_core: ^2.24.2
firebase_auth: ^4.15.2
google_sign_in: ^6.1.6 # For Google Sign-In
# sign_in_with_apple: ^6.1.1 # For Apple Sign-In (if needed)
# flutter_facebook_auth: ^6.1.1 # For Facebook Sign-In (if needed)
Run flutter pub get to fetch the packages.
Initialize Firebase in your main.dart file:
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(); // For Android/iOS
// If targeting web, you might need specific options:
// await Firebase.initializeApp(
// options: DefaultFirebaseOptions.currentPlatform, // Generate this with flutterfire config
// );
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter SSO Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const AuthScreen(),
);
}
}
Implementing Google Sign-In (SSO Example)
Google Sign-In is a common SSO method. Here's how to integrate it:
1. Create an Authentication Service
It's good practice to abstract authentication logic into a separate service.
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn _googleSignIn = GoogleSignIn();
Stream<User?> get user {
return _auth.authStateChanges();
}
Future<User?> signInWithGoogle() async {
try {
// Trigger the authentication flow
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
// Obtain the auth details from the request
final GoogleSignInAuthentication googleAuth = await googleUser!.authentication;
// Create a new credential
final AuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
// Sign in to Firebase with the credential
UserCredential userCredential = await _auth.signInWithCredential(credential);
return userCredential.user;
} catch (e) {
print(e.toString());
return null;
}
}
Future<void> signOut() async {
try {
await _googleSignIn.signOut(); // Sign out from Google
await _auth.signOut(); // Sign out from Firebase
} catch (e) {
print(e.toString());
}
}
}
2. Build Your Authentication UI
Create a simple screen where users can sign in and view their status.
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:sso_demo/auth_service.dart'; // Assuming auth_service.dart is in the same directory
class AuthScreen extends StatefulWidget {
const AuthScreen({super.key});
@override
State<AuthScreen> createState() => _AuthScreenState();
}
class _AuthScreenState extends State<AuthScreen> {
final AuthService _authService = AuthService();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('SSO with Firebase & Flutter'),
),
body: StreamBuilder<User?>(
stream: _authService.user,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
User? user = snapshot.data;
if (user == null) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('You are not signed in.'),
const SizedBox(height: 20),
ElevatedButton.icon(
onPressed: () async {
User? firebaseUser = await _authService.signInWithGoogle();
if (firebaseUser != null) {
print('Signed in as: ${firebaseUser.displayName}');
} else {
print('Google Sign-In failed.');
}
},
icon: Image.asset(
'assets/google_logo.png', // Add a Google logo image to your assets
height: 24.0,
),
label: const Text('Sign in with Google'),
),
// Add buttons for other providers here
],
),
);
} else {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Welcome, ${user.displayName ?? user.email}!'),
if (user.photoURL != null)
Padding(
padding: const EdgeInsets.all(8.0),
child: CircleAvatar(
radius: 40,
backgroundImage: NetworkImage(user.photoURL!),
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () async {
await _authService.signOut();
print('Signed out.');
},
child: const Text('Sign Out'),
),
],
),
);
}
}
return const Center(child: CircularProgressIndicator());
},
),
);
}
}
Remember to add a Google logo image to your assets folder and update pubspec.yaml to include it:
flutter:
uses-material-design: true
assets:
- assets/google_logo.png
Integrating Other SSO Providers
The pattern for integrating other providers like Apple or Facebook is very similar:
- Enable Provider in Firebase Console: Ensure the provider is enabled in Firebase Authentication settings.
- Platform-Specific Setup: Each provider requires specific setup steps in their respective developer consoles (e.g., Apple Developer Program for Apple Sign-In, Facebook Developer for Facebook Login) and often native project configurations (e.g., URL schemes).
- Add Flutter Package: Include the relevant Flutter package (e.g.,
sign_in_with_apple,flutter_facebook_auth). - Implement Sign-In Logic: Use the package's methods to get an authentication credential (token) from the provider, then pass it to
FirebaseAuth.instance.signInWithCredential()using the appropriateAuthCredentialconstructor (e.g.,FacebookAuthProvider.credential(),AppleAuthProvider.credential()).
Example Sketch for Apple Sign-In:
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
// ... other imports
Future<User?> signInWithApple() async {
try {
final AuthorizationCredentialAppleID appleCredential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
);
final AuthCredential credential = AppleAuthProvider.credential(
idToken: appleCredential.identityToken,
accessToken: appleCredential.authorizationCode,
);
UserCredential userCredential = await _auth.signInWithCredential(credential);
return userCredential.user;
} catch (e) {
print(e.toString());
return null;
}
}
Managing User Sessions and State
Firebase Authentication provides a powerful stream, FirebaseAuth.instance.authStateChanges(), which notifies you whenever the user's sign-in state changes (sign-in, sign-out, token refresh). This is what we used in the StreamBuilder in the AuthScreen to dynamically update the UI.
You can also access the current user at any time using FirebaseAuth.instance.currentUser. This will be null if no user is signed in.
Account Linking for Enhanced SSO
A powerful feature of Firebase Auth for SSO is account linking. Imagine a user first signs in with Google, then later tries to sign in with their email/password using the *same* email address. Firebase can detect this and allow you to link these accounts, so they refer to the same user profile in Firebase. This is crucial for a seamless SSO experience.
When a user tries to sign in with a new provider, but their email is already associated with an existing Firebase account (from another provider), Firebase will throw a FirebaseAuthException with the code account-exists-with-different-credential. You can then prompt the user to sign in with their original provider, and then use user.linkWithCredential(newCredential) to link the new provider to the existing account.
// Example (conceptual) of linking
Future<User?> linkGoogleAccount(User existingUser, AuthCredential googleCredential) async {
try {
UserCredential userCredential = await existingUser.linkWithCredential(googleCredential);
return userCredential.user;
} on FirebaseAuthException catch (e) {
if (e.code == 'credential-already-in-use') {
print('This Google account is already linked to another user.');
// Handle this case, perhaps by prompting the user to sign in with the Google account instead.
} else {
print(e.toString());
}
return null;
}
}
Best Practices and Considerations
- Error Handling: Always implement robust try-catch blocks to gracefully handle authentication errors and provide meaningful feedback to the user.
- UI/UX: Clearly indicate which sign-in method the user is choosing. Provide intuitive flows for new users, existing users, and account linking scenarios.
- Security Rules: Leverage Firebase Security Rules to protect your data, ensuring that only authenticated and authorized users can access or modify specific parts of your database.
- Token Management: Firebase Auth handles token refreshing automatically, so you typically don't need to manage this manually.
- Testing: Thoroughly test all sign-in flows, including sign-up, sign-in, sign-out, and account linking for each provider on all target platforms.
- Provider Selection: Offer a selection of SSO providers that are relevant to your target audience. Don't overwhelm users with too many options.
Conclusion
Implementing Single Sign-On in a Flutter application using Firebase Authentication offers a powerful combination for delivering a secure, convenient, and consistent user experience across multiple platforms. By leveraging Firebase's managed authentication services and Flutter's expressive UI capabilities, developers can significantly streamline the authentication process, allowing users to focus on your app's core functionality rather than managing multiple logins. With a few simple steps and well-structured code, you can unlock the full potential of SSO for your Flutter projects.