| name | flutter-social-login |
| description | Flutter social login (Google + Apple Sign-In) with Firebase Auth. Use when setting up or debugging Google/Apple sign-in in Flutter apps, configuring OAuth client IDs, serverClientId, or handling platform-specific login flows. Triggers on keywords like "Google Sign-In", "Apple Sign-In", "social login", "serverClientId", "OAuth client", "sign_in_with_apple", "google_sign_in". |
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_in v7+ (uses GoogleSignIn.instance.initialize() + authenticate() API)
sign_in_with_apple ^6.0.0
firebase_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 —
clientId MUST 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), signInWithCredential will fail with Error 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 with client_type: 3.
2. Google Sign-In Setup
A. Recommended: google_sign_in v7 native flow + signInWithCredential
[!IMPORTANT]
Use google_sign_in v7 authenticate() + Firebase signInWithCredential(). 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 throw GoogleSignInException(code: clientConfigurationError, serverClientId must be provided on Android). The initialize() call auto-detects serverClientId from the default_web_client_id resource 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 linkWithCredential with google_sign_in v7 credentials. v7 removed accessToken from GoogleSignInAuthentication, and linkWithCredential with only idToken causes Error code:40 (INVALID_IDP_RESPONSE). Use signInWithCredential directly 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, use user.authorizationClient.authorizationForScopes(scopes) or
clientAuthorizationTokensForScopes() separately. Firebase Auth only needs the idToken.
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.json must contain a Web OAuth client (client_type: 3) in BOTH the oauth_client array AND the other_platform_oauth_client section. The Gradle plugin generates default_web_client_id from the oauth_client array, NOT from other_platform_oauth_client. If it's only in other_platform_oauth_client, initialize() will fail with serverClientId 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.json from Firebase Console
- Or manually add the
client_type: 3 entry to the oauth_client array (copy the client_id from other_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 signInWithProvider only if you want zero google_sign_in dependency 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.applesignin to Runner.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.authorizationCode to OAuthProvider.credential(). Without it, Firebase returns invalid-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 configure auto-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 point firebase_options.dart to a secondary app.
Always verify after running flutterfire configure:
grep -A2 "iosBundleId\|appId" lib/firebase_options.dart
Fix if wrong:
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',[])]"
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 with requires-recent-login, no data is touched
- Delete cloud data after — auth token remains valid briefly
- If cloud cleanup fails, return
partialSuccess (not success) 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.