# Flutter Social Login

> 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". Use when this capability is needed.

- Skill: `tomevault-io/flutter-social-login` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/flutter-social-login`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/flutter-social-login/raw
- Safety review: pending (external: skill-scanner PASS, skillspector WARNING)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/flutter-social-login

---


# 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:
> ```bash
> 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)**

```dart
// 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)**

```dart
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):**
```dart
// 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`.

```json
"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`:
1. Ensure Google provider is enabled in Firebase Console → Authentication
2. Re-download `google-services.json` from Firebase Console
3. 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
1. App ID → Enable "Sign in with Apple" capability
2. Regenerate provisioning profile

#### B. Xcode
- Add `com.apple.developer.applesignin` to `Runner.entitlements`

#### C. Flutter Code — Apple Sign-In

```dart
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

```dart
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`:**
```bash
# Check firebase_options.dart iOS values
grep -A2 "iosBundleId\|appId" lib/firebase_options.dart
```

**Fix if wrong:**
```bash
# 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:

```dart
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.

---
> Source: [ImL1s/flutter-claude-skills](https://github.com/ImL1s/flutter-claude-skills) — distributed by [TomeVault](https://tomevault.io).
<!-- tomevault:4.0:skill_md:2026-06-16 -->

