Flutter & Firebase Auth: Implementing SSO with Multiple OAuth2 Providers
Modern applications frequently require users to authenticate seamlessly using their existing social media or enterprise accounts. Single Sign-On (SSO) simplifies this process, enhancing user experience and improving security. When building cross-platform applications with Flutter, Firebase Authentication provides a robust and flexible backend solution for implementing SSO with various OAuth2 providers.
This article will guide you through setting up a Flutter application to authenticate users using multiple OAuth2 providers like Google, Facebook, and Apple, all powered by Firebase Auth. We'll cover the necessary configurations, package installations, and code implementations to achieve a unified authentication flow.
Prerequisites
- A Flutter development environment set up.
- A Firebase project configured.
- Familiarity with basic Flutter development.
- Basic understanding of Firebase Authentication.
1. Firebase Project Setup
First, you need to enable the desired authentication methods in your Firebase project.
1.1. Create/Configure Firebase Project
If you haven't already, create a new Firebase project or use an existing one. Register your Flutter application with Firebase by adding your Android package name (applicationId) and SHA-1/SHA-256 certificates (for Google Sign-In on Android) and your iOS bundle ID.
- Download
google-services.jsonfor Android and place it inandroid/app/. - Download
GoogleService-Info.plistfor iOS and place it inios/Runner/.
1.2. Enable Authentication Providers
Navigate to the Firebase Console -> Authentication -> Sign-in method tab. Enable the following providers:
- Google:
- Enable it. Firebase typically auto-configures the web client ID.
- Ensure your Flutter app's SHA-1 fingerprint is added in Project Settings -> Android apps.
- Facebook:
- You'll need an app created in the Facebook Developer Console.
- Provide the Facebook App ID and App Secret.
- Add the OAuth redirect URI from Firebase to your Facebook app's settings (Products -> Facebook Login -> Settings -> Valid OAuth Redirect URIs).
- Apple:
- Enable it.
- You'll need to register your bundle ID with Apple, enable Sign In with Apple capability, and potentially configure a Services ID (for web-based flow, not strictly necessary for native iOS).
2. Flutter Project Setup
Next, we need to add the necessary dependencies to our Flutter project.
2.1. Add Dependencies
Open your pubspec.yaml file and add the following dependencies:
dependencies:
flutter:
sdk: flutter
firebase_core: ^2.24.2
firebase_auth: ^4.15.2
google_sign_in: ^6.1.6 # For Google Sign-In
flutter_facebook_auth: ^6.1.0 # For Facebook Sign-In
sign_in_with_apple: ^6.1.0 # For Apple Sign-In
Run flutter pub get to install these packages.
2.2. Initialize Firebase
Ensure Firebase is initialized correctly in your main.dart file.
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'firebase_options.dart'; // Generated by FlutterFire CLI
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 MaterialApp(
title: 'Flutter Firebase SSO',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: AuthScreen(),
);
}
}
The firebase_options.dart file is generated by the FlutterFire CLI. If you haven't done so, install the CLI (dart pub global activate flutterfire_cli) and run flutterfire configure in your project root.
2.3. Platform-Specific Configurations
Android
- No additional code configuration for Facebook beyond
google-services.json(already covered).
iOS
- Facebook:
Add the following to your
ios/Runner/Info.plistfile:<key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleURLSchemes</key> <array> <string>fb[YOUR_FACEBOOK_APP_ID]</string> </array> </dict> </array> <key>FacebookAppID</key> <string>[YOUR_FACEBOOK_APP_ID]</string> <key>FacebookDisplayName</key> <string>[YOUR_APP_DISPLAY_NAME]</string> <key>LSApplicationQueriesSchemes</key> <array> <string>fbapi</string> <string>fb-messenger-share-api</string> <string>fbauth2</string> <string>fbshareextension</string> </array>Replace
[YOUR_FACEBOOK_APP_ID]and[YOUR_APP_DISPLAY_NAME]. - Apple Sign-In:
In Xcode, navigate to your project settings, select your target, and go to the "Signing & Capabilities" tab. Click "+ Capability" and add "Sign In with Apple".
3. Implementing Authentication Logic
Now, let's write the Flutter code to handle sign-in with each provider.
3.1. Google Sign-In
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
Future<UserCredential?> 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;
if (googleAuth?.accessToken != null && googleAuth?.idToken != null) {
// Create a new credential
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth!.accessToken,
idToken: googleAuth.idToken,
);
// Sign in to Firebase with the credential
return await FirebaseAuth.instance.signInWithCredential(credential);
}
} on FirebaseAuthException catch (e) {
print("Firebase Auth Error for Google: ${e.message}");
} catch (e) {
print("Google Sign-In Error: $e");
}
return null;
}
3.2. Facebook Sign-In
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
Future<UserCredential?> signInWithFacebook() async {
try {
final LoginResult result = await FlutterFacebookAuth.instance.login();
if (result.status == LoginStatus.success) {
// Get the access token
final AccessToken? accessToken = result.accessToken;
if (accessToken != null) {
// Create a Facebook credential
final credential = FacebookAuthProvider.credential(accessToken.token);
// Sign in to Firebase with the credential
return await FirebaseAuth.instance.signInWithCredential(credential);
}
} else if (result.status == LoginStatus.cancelled) {
print('Facebook login cancelled.');
} else {
print('Facebook login failed: ${result.message}');
}
} on FirebaseAuthException catch (e) {
print("Firebase Auth Error for Facebook: ${e.message}");
} catch (e) {
print("Facebook Sign-In Error: $e");
}
return null;
}
3.3. Apple Sign-In
import 'package:firebase_auth/firebase_auth.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
Future<UserCredential?> signInWithApple() async {
try {
final AuthorizationCredentialAppleID appleCredential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDButtonType.signIn,
AppleIDButtonType.signUp,
AppleIDButtonType.continueWithApple,
],
webAuthenticationOptions: ( // Optional, for web-based flow on non-Apple devices
// For example, if you want to support Apple Sign In on Android/Web
// you'd need to configure a Firebase Apple OAuth provider
// and specify clientId and redirectUri here.
// For native iOS, this can often be null.
// Check Firebase console for your Apple provider's settings.
// clientId: 'YOUR_FIREBASE_APPLE_OAUTH_CLIENT_ID',
// redirectUri: Uri.parse('YOUR_FIREBASE_APPLE_REDIRECT_URI'),
),
);
if (appleCredential.identityToken != null) {
// Create a Firebase credential from the Apple credential
final credential = AppleAuthProvider.credential(
idToken: appleCredential.identityToken,
accessToken: appleCredential.authorizationCode, // The authorizationCode is often used as access token by Firebase
);
// Sign in to Firebase with the credential
return await FirebaseAuth.instance.signInWithCredential(credential);
}
} on FirebaseAuthException catch (e) {
print("Firebase Auth Error for Apple: ${e.message}");
} on SignInWithAppleAuthorizationException catch (e) {
print("Apple Sign-In Error: ${e.code} - ${e.message}");
// Handle specific Apple sign-in errors like cancellation
} catch (e) {
print("Generic Apple Sign-In Error: $e");
}
return null;
}
3.4. Generic OAuth2 Providers (e.g., GitHub, Twitter, Microsoft)
For providers not natively supported by client-side SDKs, Firebase offers a generic OAuthProvider. This typically involves opening a web browser for the authentication flow and redirecting back to your app.
import 'package:firebase_auth/firebase_auth.dart';
Future<UserCredential?> signInWithGenericOAuth(String providerId) async {
try {
// For example, 'github.com', 'twitter.com', 'microsoft.com'
final OAuthProvider oAuthProvider = OAuthProvider(providerId);
oAuthProvider.addScope('user:email'); // Example scope for GitHub
// For web and mobile flows that don't use SDKs
// The signInWithPopup or signInWithRedirect method will handle the web view.
// On mobile, this will open the default browser.
return await FirebaseAuth.instance.signInWithProvider(oAuthProvider);
} on FirebaseAuthException catch (e) {
print("Firebase Auth Error for $providerId: ${e.message}");
} catch (e) {
print("Generic OAuth Sign-In Error for $providerId: $e");
}
return null;
}
4. Handling SSO and Account Linking
Firebase Auth automatically handles account linking when users sign in with different providers but use the same verified email address. For example, if a user signs in with Google using [email protected], and then later signs in with Facebook also using [email protected], Firebase links these accounts, and the User object will reflect both provider IDs.
If a user attempts to sign in with a new provider, but an existing Firebase account already exists with a *different* email address, Firebase might prompt you to link the accounts explicitly. You can achieve this using currentUser.linkWithCredential().
Future<UserCredential?> linkAccounts(AuthCredential credential) async {
try {
final User? user = FirebaseAuth.instance.currentUser;
if (user != null) {
return await user.linkWithCredential(credential);
}
} on FirebaseAuthException catch (e) {
if (e.code == 'credential-already-in-use') {
print('This credential is already associated with another account.');
// Handle merging accounts or showing an error
} else {
print('Error linking account: ${e.message}');
}
} catch (e) {
print('Unknown error linking account: $e');
}
return null;
}
5. User Management and UI
You can observe authentication state changes to update your UI accordingly and manage user sessions.
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class AuthScreen extends StatefulWidget {
const AuthScreen({super.key});
@override
State<AuthScreen> createState() => _AuthScreenState();
}
class _AuthScreenState extends State<AuthScreen> {
User? _currentUser;
@override
void initState() {
super.initState();
FirebaseAuth.instance.authStateChanges().listen((User? user) {
setState(() {
_currentUser = user;
});
});
}
Future<void> _signOut() async {
await FirebaseAuth.instance.signOut();
await GoogleSignIn().signOut(); // Also sign out from Google
await FlutterFacebookAuth.instance.logOut(); // Also sign out from Facebook
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Firebase SSO')),
body: Center(
child: _currentUser == null
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () async {
await signInWithGoogle();
},
child: const Text('Sign in with Google'),
),
ElevatedButton(
onPressed: () async {
await signInWithFacebook();
},
child: const Text('Sign in with Facebook'),
),
ElevatedButton(
onPressed: () async {
await signInWithApple(); // Only visible on iOS
},
child: const Text('Sign in with Apple'),
),
ElevatedButton(
onPressed: () async {
await signInWithGenericOAuth('github.com');
},
child: const Text('Sign in with GitHub'),
),
],
)
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Welcome, ${_currentUser!.displayName ?? _currentUser!.email}!'),
Text('Provider(s): ${_currentUser!.providerData.map((e) => e.providerId).join(', ')}'),
ElevatedButton(
onPressed: _signOut,
child: const Text('Sign Out'),
),
],
),
),
);
}
}
Conclusion
Implementing SSO with multiple OAuth2 providers in Flutter using Firebase Authentication significantly enhances user experience by offering flexible and familiar sign-in options. By following the steps outlined in this guide – from Firebase console configurations to platform-specific setups and Flutter code – you can create a robust authentication system that integrates seamlessly with major identity providers. Firebase's automatic account linking further simplifies user management, making it an excellent choice for modern cross-platform applications.