Flutter Social Login with Firebase Auth
Complete guide for implementing Google and Apple Sign-In in Flutter apps using Firebase Authentication.
Package Versions
This skill targets:
google_sign_inv7+ (usesGoogleSignIn.instance.initialize()+authenticate()API)sign_in_with_apple^6.0.0firebase_auth^5.0.0
Platform Strategy
| Platform | Google Sign-In | Apple Sign-In |
|---|---|---|
| Android | ✅ Native (Credential Manager) | ❌ Skip (requires complex web auth setup) |
| iOS | ✅ Native | ✅ Native |
[!IMPORTANT] Apple Sign-In on Android requires a Services ID, .p8 key, redirect URL, and web authentication flow. Unless you specifically need it, skip Apple Sign-In on Android and show it only on iOS.
Setup Checklist
1. Firebase Auth Providers
Enable both providers in Firebase Console → Authentication → Sign-in method:
- Google: Enabled —
clientIdMUST be the actual Web OAuth client ID (e.g.PROJECT_NUMBER-xxx.apps.googleusercontent.com), NOT"auto". If set to"auto"(e.g. via CLI),signInWithCredentialwill fail withError code:40(INVALID_IDP_RESPONSE). - Apple: Enabled with
clientId= your bundle ID (e.g.com.example.app)
Or via CLI (see firebase-auth-manager skill for API details).
[!CAUTION] If you enabled Google Sign-In via the Identity Toolkit Admin API with
clientId: "auto", you MUST update it with the actual Web OAuth client ID:ACCESS_TOKEN=$(gcloud auth print-access-token) curl -s -X PATCH \ "https://identitytoolkit.googleapis.com/admin/v2/projects/${PROJECT_ID}/defaultSupportedIdpConfigs/google.com?updateMask=clientId" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -H "x-goog-user-project: ${PROJECT_ID}" \ -d "{\"clientId\": \"YOUR_WEB_CLIENT_ID\"}"The Web client ID can be found in
google-services.json→oauth_client→ entry withclient_type: 3.
2. Google Sign-In Setup
A. Recommended: google_sign_in v7 native flow + signInWithCredential
[!IMPORTANT] Use
google_sign_inv7authenticate()+ FirebasesignInWithCredential(). This gives native Credential Manager on Android and native Google Sign-In on iOS.DO NOT use
signInWithProvider(GoogleAuthProvider())— on Android this opens a Chrome Custom Tab (web-based OAuth), NOT native Credential Manager. It also requires a Web OAuth client to be fully configured in GCP Console.
Step 1: Initialize at app startup (CRITICAL for Android)
// In main.dart, after Firebase.initializeApp():
import 'package:google_sign_in/google_sign_in.dart';
try {
await GoogleSignIn.instance.initialize();
} catch (e) {
debugPrint('GoogleSignIn initialize failed: $e');
}
[!CAUTION] Without
initialize(), Android will throwGoogleSignInException(code: clientConfigurationError, serverClientId must be provided on Android). Theinitialize()call auto-detectsserverClientIdfrom thedefault_web_client_idresource generated by the google-services Gradle plugin.
Step 2: Sign-in implementation (v7.1+ event-stream API)
import 'dart:async';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:firebase_auth/firebase_auth.dart';
/// Call once at app startup (after initialize()):
void _listenToGoogleAuth() {
GoogleSignIn.instance.authenticationEvents
.listen((event) async {
switch (event) {
case GoogleSignInAuthenticationEventSignIn():
final user = event.user;
// Get server auth tokens for Firebase
final tokens = await GoogleSignIn.instance
.serverAuthTokensForUser(user);
if (tokens?.idToken != null) {
final credential = GoogleAuthProvider.credential(
idToken: tokens!.idToken,
);
await FirebaseAuth.instance.signInWithCredential(credential);
}
case GoogleSignInAuthenticationEventSignOut():
await FirebaseAuth.instance.signOut();
}
});
}
/// Trigger interactive sign-in:
Future<void> signInWithGoogle() async {
try {
// First try lightweight (silent) auth
final result = await GoogleSignIn.instance
.attemptLightweightAuthentication();
if (result == null) {
// No cached credential — trigger interactive sign-in
await GoogleSignIn.instance.authenticate(
scopeHint: ['email', 'profile'],
);
}
} on GoogleSignInException catch (e) {
if (e.code == GoogleSignInExceptionCode.canceled) {
return; // User cancelled
}
rethrow;
}
}
[!CAUTION] DO NOT use
linkWithCredentialwithgoogle_sign_inv7 credentials. v7 removedaccessTokenfromGoogleSignInAuthentication, andlinkWithCredentialwith onlyidTokencausesError code:40(INVALID_IDP_RESPONSE). UsesignInWithCredentialdirectly instead.
For re-authentication (e.g. before account deletion):
// v7: Use authenticate() to trigger interactive sign-in,
// then get server auth tokens for the signed-in user.
await GoogleSignIn.instance.authenticate(scopeHint: ['email']);
// Listen to authenticationEvents stream for the result,
// then use the idToken to reauthenticate with Firebase.
[!NOTE] In v7, authentication and authorization are separated.
authenticate()handles sign-in only. For Google API access tokens, useuser.authorizationClient.authorizationForScopes(scopes)orclientAuthorizationTokensForScopes()separately. Firebase Auth only needs theidToken.
B. v7 Breaking Changes from v6
| v6 API | v7 API |
|---|---|
GoogleSignIn(serverClientId: '...') |
GoogleSignIn.instance.initialize(serverClientId: '...') |
googleSignIn.signIn() |
GoogleSignIn.instance.authenticate(scopeHint: [...]) |
googleSignIn.signInSilently() |
GoogleSignIn.instance.attemptLightweightAuthentication() |
authentication.accessToken |
Removed — use authorizationClient.authorizeScopes() |
GoogleSignIn.disconnect() |
GoogleSignIn.instance.disconnect() |
C. Android Configuration — google-services.json Requirements
[!CAUTION] The
google-services.jsonmust contain a Web OAuth client (client_type: 3) in BOTH theoauth_clientarray AND theother_platform_oauth_clientsection. The Gradle plugin generatesdefault_web_client_idfrom theoauth_clientarray, NOT fromother_platform_oauth_client. If it's only inother_platform_oauth_client,initialize()will fail withserverClientId must be provided on Android.
"oauth_client": [
{
"client_id": "YOUR_PROJECT-xxx.apps.googleusercontent.com",
"client_type": 1,
"android_info": { ... }
},
{
"client_id": "YOUR_PROJECT-yyy.apps.googleusercontent.com",
"client_type": 3
}
],
...
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "YOUR_PROJECT-yyy.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
This Web client is auto-created by Firebase when you enable Google Sign-In provider. If missing from oauth_client:
- Ensure Google provider is enabled in Firebase Console → Authentication
- Re-download
google-services.jsonfrom Firebase Console - Or manually add the
client_type: 3entry to theoauth_clientarray (copy theclient_idfromother_platform_oauth_client)
D. Why NOT signInWithProvider
| Aspect | signInWithProvider |
google_sign_in v7 + signInWithCredential |
|---|---|---|
| Android UX | Chrome Custom Tab (web) | Native Credential Manager |
| iOS UX | Native sheet | Native sheet |
Requires google_sign_in package |
No | Yes |
| Requires Web OAuth client | Yes (for Android) | Yes (for serverClientId) |
| Token control | None (Firebase manages) | Full (get idToken) |
| Google API access | No | Yes (via authorizeScopes) |
Choose
signInWithProvideronly if you want zerogoogle_sign_independency AND don't mind the Chrome Custom Tab UX on Android.
3. Apple Sign-In Setup (iOS Only)
A. Apple Developer Console
- App ID → Enable "Sign in with Apple" capability
- Regenerate provisioning profile
B. Xcode
- Add
com.apple.developer.applesignintoRunner.entitlements
C. Flutter Code — Apple Sign-In
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'dart:convert';
import 'dart:math';
import 'package:crypto/crypto.dart';
Future<UserCredential?> signInWithApple() async {
// Generate nonce
final rawNonce = _generateNonce();
final hashedNonce = sha256.convert(utf8.encode(rawNonce)).toString();
final appleCredential = await SignInWithApple.getAppleIDCredential(
scopes: [AppleIDAuthorizationScopes.email],
nonce: hashedNonce,
);
final oauthCredential = OAuthProvider('apple.com').credential(
idToken: appleCredential.identityToken,
rawNonce: rawNonce,
accessToken: appleCredential.authorizationCode, // CRITICAL for Firebase
);
return FirebaseAuth.instance.signInWithCredential(oauthCredential);
}
String _generateNonce([int length = 32]) {
final charset = '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._';
final random = Random.secure();
return List.generate(length, (_) => charset[random.nextInt(charset.length)]).join();
}
[!WARNING] You must pass
accessToken: appleCredential.authorizationCodetoOAuthProvider.credential(). Without it, Firebase returnsinvalid-credential.
4. Platform-Conditional UI
import 'dart:io';
Row(
children: [
// Apple: iOS only
if (Platform.isIOS) ...[
Expanded(
child: OutlinedButton.icon(
onPressed: () => signInWithApple(),
icon: const Icon(Icons.apple),
label: const Text('Apple'),
),
),
const SizedBox(width: 12),
],
// Google: both platforms
Expanded(
child: OutlinedButton.icon(
onPressed: () => signInWithGoogle(),
icon: const Icon(Icons.g_mobiledata),
label: const Text('Google'),
),
),
],
)
Common Pitfalls
| Problem | Cause | Fix |
|---|---|---|
serverClientId must be provided on Android |
Missing GoogleSignIn.instance.initialize() call |
Add await GoogleSignIn.instance.initialize() at app startup after Firebase.initializeApp() |
serverClientId auto-detect fails |
client_type: 3 only in other_platform_oauth_client, not in oauth_client |
Add client_type: 3 entry to oauth_client array (Gradle plugin reads oauth_client, not other_platform_oauth_client) |
Error code:40 / INVALID_IDP_RESPONSE on signInWithCredential |
Firebase Google provider clientId set to "auto" instead of actual Web client ID |
Update provider via Identity Toolkit API: set clientId to the Web OAuth client ID from google-services.json (client_type: 3 in oauth_client) |
Error code:40 / INVALID_IDP_RESPONSE on linkWithCredential |
v7 removed accessToken from GoogleSignInAuthentication; linkWithCredential needs both tokens |
Use signInWithCredential directly instead of linkWithCredential |
| Google Sign-In opens Chrome Custom Tab (web) | Using signInWithProvider(GoogleAuthProvider()) |
Switch to google_sign_in v7 authenticate() + signInWithCredential() |
google-services.json has empty oauth_client |
No SHA-1 fingerprints registered in Firebase | Add SHA-1s via firebase apps:android:sha:create, then re-download config |
CredManProvService: GetCredentialResponse error |
SHA-1 mismatch on Android Credential Manager | Register ALL 3 SHA-1s (debug, release, Play Store signing key), re-download google-services.json |
Apple invalid-credential |
Missing accessToken in credential |
Pass authorizationCode as accessToken |
flutterfire configure wrong iOS app |
Detects ShareExtension bundle ID | Verify firebase_options.dart points to main app |
GoogleService-Info.plist wrong bundle |
Re-downloaded for wrong app | Use firebase apps:sdkconfig ios APP_ID -o path with correct app ID |
| Google Sign-In fails silently on Android | Missing SHA fingerprints | Add debug + release + app-signing SHA-1/SHA-256 to Firebase |
flutterfire configure Gotchas
[!CAUTION]
flutterfire configureauto-detects iOS targets by bundle ID. If your project has multiple targets (e.g. Share Extension, Widget Extension), it may register the wrong one and pointfirebase_options.dartto a secondary app.
Always verify after running flutterfire configure:
# Check firebase_options.dart iOS values
grep -A2 "iosBundleId\|appId" lib/firebase_options.dart
Fix if wrong:
# List all iOS apps to find correct appId
ACCESS_TOKEN=$(gcloud auth print-access-token)
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
"https://firebase.googleapis.com/v1beta1/projects/YOUR_PROJECT/iosApps" \
| python3 -c "import sys,json; [print(f\"{a['appId']} → {a.get('bundleId','?')}\") for a in json.load(sys.stdin).get('apps',[])]"
# Download correct GoogleService-Info.plist
firebase apps:sdkconfig ios CORRECT_APP_ID -o ios/Runner/GoogleService-Info.plist
Account Deletion Best Practice
When implementing account deletion with social login:
enum DeleteResult { success, partialSuccess, requiresReauth, failed }
- Delete auth user first (
user.delete()) — if it fails withrequires-recent-login, no data is touched - Delete cloud data after — auth token remains valid briefly
- If cloud cleanup fails, return
partialSuccess(notsuccess) for compliance transparency - Record failed cleanups to Crashlytics for server-side follow-up
Related skills
firebase-auth-manager— enable/disable auth providers via CLI.firebase-appcheck-manager— use alongside flutter-social-login for securing authentication endpoints with App Check.