Flutter Google Sign‑In: A Complete Guide for Android, iOS, and Web

Learn how to implement Google Sign‑In in Flutter on Android, iOS, and web—setup, code, Firebase integration, tokens, testing, and best practices.

ASOasis
9 min read
Flutter Google Sign‑In: A Complete Guide for Android, iOS, and Web

Image used for representation purposes only.

Overview

Google Sign‑In is one of the fastest ways to add secure, low‑friction authentication to a Flutter app. This guide walks you through setup for Android, iOS, and web; explains when to use the google_sign_in plugin alone versus pairing it with Firebase Authentication; and shows production‑grade patterns for token handling, UX, and testing.

By the end, you’ll be able to:

  • Configure OAuth 2.0 credentials for each platform
  • Implement the sign‑in, silent sign‑in, and sign‑out flows
  • Exchange Google tokens for Firebase sessions or your own backend sessions
  • Avoid the most common configuration pitfalls

Architecture choices

You have three common options:

  1. Google Sign‑In only (client‑side auth)
  • Use the google_sign_in plugin to authenticate the user with Google and obtain an ID token (and sometimes a server auth code).
  • You manage your own backend session if you need one, verifying the Google ID token server‑side.
  • Good when you just need Google identity, minimal infrastructure, or custom auth.
  1. Google Sign‑In + Firebase Authentication
  • Use google_sign_in to get Google credentials, then pass them to Firebase Authentication to create a Firebase session.
  • Benefits: mature SDKs, easy user/session management, built‑in token refresh, secure rules for Firestore/Storage.
  1. Server‑assisted OAuth (offline access)
  • Request a serverAuthCode (with access_type=offline) and exchange it on the server for long‑lived refresh tokens.
  • Good for backend APIs that call Google services on the user’s behalf.

Prerequisites

  • Flutter SDK installed and a working device/emulator (Android Studio or Xcode for simulators)
  • A Google Cloud (or Firebase) project
  • Unique app identifiers:
    • Android applicationId (e.g., com.example.app)
    • iOS bundle identifier (e.g., com.example.app)
    • Web origin (e.g., https://localhost:5000 during development and your production domain)

Packages to install

From your Flutter project root:

flutter pub add google_sign_in
# Optional if you will use Firebase Authentication
flutter pub add firebase_core firebase_auth

If you use Firebase, also run the platform setup tooling after you add Firebase to your project:

# Optional (if using Firebase). Sets up platform config files automatically.
# Requires the FlutterFire CLI to be installed.
flutterfire configure

Configure OAuth credentials

Google requires a separate OAuth 2.0 client ID for each platform build target. You’ll create client IDs for Android, iOS, and Web. If you use Firebase, much of this is generated when you add your app in the console and download the config files.

Android setup

  • Create an Android OAuth client. You’ll need:
    • Package name (applicationId)
    • SHA‑1 or SHA‑256 signing certificate fingerprints (debug and release differ)
  • For debug builds, generate fingerprints with:
# Mac/Linux
keytool -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore -storepass android -keypass android

# Windows (PowerShell)
keytool -list -v -alias androiddebugkey -keystore $env:USERPROFILE\.android\debug.keystore -storepass android -keypass android
  • Add the fingerprints to your OAuth client (or Firebase console). If using Firebase, download and place google-services.json in android/app/.
  • Ensure your android/app/build.gradle applicationId matches the one used to create the client.

iOS setup

  • Create an iOS OAuth client using your bundle identifier.
  • If using Firebase, download GoogleService-Info.plist and add it to ios/Runner/.
  • Add a URL Type to your Xcode project (Runner target > Info > URL Types):
    • Identifier: your app identifier (any string)
    • URL Schemes: the reversed client ID (e.g., com.googleusercontent.apps.1234567890-abcdefg)

This URL scheme enables the OAuth redirect back to your app.

Web setup

  • Create a Web OAuth client.
  • Add your authorized JavaScript origins (e.g., http://localhost:5000 during development and your production domain).
  • If you’re using the default google_sign_in_web implementation, it will load the Google Identity Services under the hood. Ensure your domain/origin is authorized.

Writing the Flutter code

Below is a minimal, platform‑agnostic implementation using google_sign_in.

import 'package:flutter/material.dart';
import 'package:google_sign_in/google_sign_in.dart';

final GoogleSignIn _googleSignIn = GoogleSignIn(
  // Request only what you need. email and profile are included by default.
  scopes: <String>[
    'email',
    // Add more scopes only if necessary, e.g. 'https://www.googleapis.com/auth/contacts.readonly'
  ],
);

class GoogleSignInButton extends StatefulWidget {
  const GoogleSignInButton({super.key});

  @override
  State<GoogleSignInButton> createState() => _GoogleSignInButtonState();
}

class _GoogleSignInButtonState extends State<GoogleSignInButton> {
  GoogleSignInAccount? _currentUser;
  bool _loading = false;
  String? _error;

  @override
  void initState() {
    super.initState();
    _googleSignIn.onCurrentUserChanged.listen((account) {
      setState(() => _currentUser = account);
    });
    // Attempt silent sign‑in on app start
    _googleSignIn.signInSilently().catchError((e) {
      // Silent sign‑in can fail if the user has never consented
    });
  }

  Future<void> _handleSignIn() async {
    setState(() { _loading = true; _error = null; });
    try {
      final account = await _googleSignIn.signIn();
      if (account == null) {
        // User canceled the flow
        return;
      }
      final auth = await account.authentication;
      debugPrint('ID token: ${auth.idToken?.substring(0, 20)}...');
      debugPrint('Access token: ${auth.accessToken?.substring(0, 10)}...');
      // Send tokens to your backend or use with Firebase Auth (see next section)
    } catch (e) {
      setState(() => _error = e.toString());
    } finally {
      if (mounted) setState(() => _loading = false);
    }
  }

  Future<void> _handleSignOut() async {
    await _googleSignIn.signOut();
  }

  @override
  Widget build(BuildContext context) {
    final user = _currentUser;
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        if (_loading) const CircularProgressIndicator(),
        if (_error != null) Text(_error!, style: const TextStyle(color: Colors.red)),
        if (user == null) ...[
          ElevatedButton.icon(
            onPressed: _loading ? null : _handleSignIn,
            icon: Image.asset('assets/google_logo.png', width: 18, height: 18),
            label: const Text('Sign in with Google'),
            style: ElevatedButton.styleFrom(backgroundColor: Colors.white, foregroundColor: Colors.black87),
          ),
        ] else ...[
          ListTile(
            leading: CircleAvatar(backgroundImage: NetworkImage(user.photoUrl ?? '')),
            title: Text(user.displayName ?? 'User'),
            subtitle: Text(user.email),
            trailing: TextButton(onPressed: _handleSignOut, child: const Text('Sign out')),
          )
        ]
      ],
    );
  }
}

Notes:

  • signInSilently() restores sessions when possible without prompting.
  • signOut() disconnects the local session; use disconnect() to revoke the granted scopes on the server.

Using Google credentials with Firebase Authentication (optional)

If your app uses Firebase services, exchange the Google tokens for a Firebase user session.

import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';

Future<UserCredential> signInWithGoogleViaFirebase() async {
  final GoogleSignInAccount? account = await GoogleSignIn().signIn();
  if (account == null) {
    throw Exception('Sign‑in aborted by user');
  }
  final GoogleSignInAuthentication auth = await account.authentication;

  // Build a Firebase credential from the Google ID/access tokens
  final credential = GoogleAuthProvider.credential(
    accessToken: auth.accessToken,
    idToken: auth.idToken,
  );

  return FirebaseAuth.instance.signInWithCredential(credential);
}

Tips for Firebase:

  • Call WidgetsFlutterBinding.ensureInitialized() and await Firebase.initializeApp() (or run flutterfire configure which wires this automatically).
  • In the Firebase console, enable Google as a sign‑in provider.

If you’re not using Firebase and you need your own backend session:

  • Request the user’s ID token from google_sign_in via account.authentication.
  • Send the ID token to your backend over HTTPS.
  • On the server, verify the ID token’s signature and claims (audience, issuer, expiry). Use an official Google auth library for your stack, or validate against the public certs. Do not trust ID tokens validated only on the client.
  • Create your own session/JWT tied to the Google subject (sub) claim.

For offline access to Google APIs (e.g., Calendar), request a serverAuthCode and exchange it server‑side for refresh tokens. Only do this if your app truly needs it.

  • Ask for the minimum scopes required. The default profile/email is ideal for sign‑in.
  • For additional Google APIs, add scopes to GoogleSignIn(scopes: […]). If you later need more permissions, use incremental authorization—prompt only when the user tries a feature requiring that scope.

UX best practices

  • Use the official “Sign in with Google” button styling and branding.
  • Keep a loading state visible during the OAuth handoff.
  • Provide a clear way to sign out and to switch accounts.
  • Persist essential user profile fields (id, email, displayName, photoUrl) in your app state; avoid blocking the UI on token refresh.

Error handling and common pitfalls

  • Android SHA mismatches: If debug sign‑in works but release fails, your release keystore SHA‑1/SHA‑256 wasn’t added to the OAuth client. Add it, then reinstall the app.
  • iOS URL scheme missing: If the browser returns but your app doesn’t receive the callback, verify the reversed client ID is added to URL Types.
  • Wrong bundle/application ID: OAuth clients are bound to identifiers—double‑check they match your actual build config.
  • Multiple client IDs: Each platform needs its own client. On web, ensure the authorized origin exactly matches the scheme, host, and port.
  • Web popup blockers: The sign‑in call should be triggered by a direct user gesture (e.g., button tap) to avoid popup blocking.
  • Silent sign‑in assumptions: signInSilently() can fail if the user cleared cookies, revoked access, or changed accounts.

Secure token handling

  • Treat ID/access tokens as secrets; never log them in production or store them in plaintext on device.
  • Prefer short‑lived tokens on the client. If your backend needs long‑lived access, keep refresh tokens only on the server.
  • Use HTTPS everywhere and pin transport to your trusted domain.
  • If you cache tokens locally, use platform‑secure storage and set explicit expirations.

Testing strategy

  • Emulators/Simulators: Validate flows on both debug and release (or TestFlight/internal testing) builds because credentials differ.
  • Real devices: Some issues appear only with real browsers/account pickers.
  • Multiple accounts: Test switching accounts and revoking access from the Google account’s security settings.
  • Network edge cases: Airplane mode, captive portals, and time skew (device clock) can break auth—show helpful messages and retry options.

Adding a polished sign‑in button

Here’s an example that matches the Google brand guidelines while remaining fully customizable:

class GoogleBrandedButton extends StatelessWidget {
  final VoidCallback onPressed;
  final bool loading;
  const GoogleBrandedButton({super.key, required this.onPressed, this.loading = false});

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: loading ? null : onPressed,
      style: ElevatedButton.styleFrom(
        backgroundColor: Colors.white,
        foregroundColor: Colors.black87,
        minimumSize: const Size.fromHeight(48),
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
      ),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        mainAxisSize: MainAxisSize.min,
        children: [
          if (!loading)
            Image.asset('assets/google_logo.png', width: 18, height: 18)
          else
            const SizedBox(width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2)),
          const SizedBox(width: 12),
          const Text('Sign in with Google'),
        ],
      ),
    );
  }
}

Linking and reauthenticating (advanced)

  • Account linking: If you have multiple providers (email/password, Apple, etc.), allow users to link Google to an existing account. In FirebaseAuth, call currentUser.linkWithCredential(…).
  • Reauthentication: For sensitive operations (delete account, change email), re‑prompt the user for Google sign‑in and pass fresh credentials to reauthenticate.

Observability and support

  • Log high‑level auth events (start, success, cancel, error code) with anonymized user IDs.
  • Add a “Report sign‑in issue” link that pre‑fills diagnostics (platform, app version, time, error message) to help your support team.

Production checklist

  • OAuth clients created for Android (debug + release), iOS, and Web
  • Correct package/bundle IDs, authorized origins/redirect URIs
  • iOS URL scheme (reversed client ID) added to Xcode project
  • Firebase Google provider enabled (if using Firebase), config files added
  • Sign‑in button branded and accessible (contrast, readable labels)
  • Error handling for cancel, network, and token expiry paths
  • Server verifies ID tokens and issues app session (if applicable)
  • Analytics/monitoring around sign‑in journey

Conclusion

Google Sign‑In in Flutter is straightforward once your platform credentials are in order. Start with the google_sign_in plugin for core identity, layer Firebase Authentication for drop‑in backend sessions, or implement a server‑verified flow for custom needs. Keep scopes minimal, handle tokens securely, and test across platforms and build types to ensure a smooth, trustworthy experience for your users.

Related Posts