Firebase Authentication — Google Sign-In & Auth State
Firebase Authentication — Google Sign-In & Auth State
Firebase Authentication provides a complete backend solution for user identity. In this lesson you will integrate Google Sign-In as an OAuth provider, learn how authStateChanges() persists authentication across app restarts, and protect your routes by redirecting unauthenticated users to a login screen. These three skills together form the foundation of every secure Flutter application.
Prerequisites & Packages
Before writing any code make sure your pubspec.yaml includes the following dependencies:
dependencies:
firebase_core: ^2.27.0
firebase_auth: ^4.19.0
google_sign_in: ^6.2.1
Run flutter pub get, then confirm your google-services.json (Android) and GoogleService-Info.plist (iOS) are placed in the correct platform directories. On Android you must also enable the Google Sign-In method in the Firebase console under Authentication → Sign-in method.
CFBundleURLTypes entry in Info.plist that contains the reversed client ID from your GoogleService-Info.plist. Omitting it causes the sign-in sheet to open and close immediately with no error.Wiring Up Google Sign-In
The sign-in flow has two steps: obtain a GoogleSignInAccount credential from the google_sign_in package, then exchange it for a Firebase UserCredential via signInWithCredential(). Wrap everything in a try/catch because network errors, cancellations, and misconfigured SHA certificates all surface here.
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn _googleSignIn = GoogleSignIn();
/// Triggers the Google OAuth consent screen and signs the user
/// into Firebase. Returns the [UserCredential] on success.
Future<UserCredential?> signInWithGoogle() async {
try {
// Step 1 — open the Google account picker
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
if (googleUser == null) return null; // user cancelled
// Step 2 — obtain auth tokens from the chosen account
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
// Step 3 — build a Firebase credential from those tokens
final OAuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
// Step 4 — sign into Firebase and get the UserCredential
return await _auth.signInWithCredential(credential);
} catch (e) {
debugPrint('Google sign-in failed: $e');
return null;
}
}
Future<void> signOut() async {
await Future.wait([
_auth.signOut(),
_googleSignIn.signOut(),
]);
}
}
FirebaseAuth.signOut() and GoogleSignIn.signOut() when logging out. If you only sign out of Firebase the Google account picker will silently re-select the previous account on the next login attempt, bypassing the account chooser entirely.Persisting Auth State with authStateChanges()
FirebaseAuth.instance.authStateChanges() returns a Stream<User?> that emits a new value whenever the user signs in, signs out, or the token is refreshed. The stream also fires once immediately on subscription — critically, Firebase caches the last known user on device, so if the user was previously signed in the stream emits a non-null User even before any network call completes. This is how you avoid forcing users to log in every time the app cold-starts.
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class AuthGate extends StatelessWidget {
const AuthGate({super.key});
@override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
// Still waiting for the first emission
if (snapshot.connectionState == ConnectionState.waiting) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
// User is signed in — go to the main app
if (snapshot.hasData) {
return const HomeScreen();
}
// No user — show the login screen
return const LoginScreen();
},
);
}
}
Place AuthGate as the home of your MaterialApp. The stream handles all transitions — sign-in, sign-out, token expiry — without any manual navigation logic in your sign-in or sign-out handlers.
Protecting Routes & Redirecting Unauthenticated Users
For apps that use named routes or a router package, the pattern is similar: read FirebaseAuth.instance.currentUser synchronously inside a redirect callback, or listen to the stream inside a ChangeNotifier / Riverpod provider. The AuthGate approach above already covers navigation-level protection for simple apps.
FirebaseAuth.instance.currentUser can be non-null for a split second even after signOut() completes because Firebase updates the cache asynchronously. Always rely on the authStateChanges() stream for reactive navigation rather than reading currentUser directly in a build method.Putting It All Together
Wire the service into a login screen that calls AuthService().signInWithGoogle() on button press. Because AuthGate listens to the stream, a successful sign-in automatically navigates the user to HomeScreen — no Navigator.push() required.
signInWithCredential() to exchange an OAuth token for a Firebase session, (2) authStateChanges() to reactively drive navigation, and (3) signing out of both Firebase and the OAuth provider to fully clear the session. Together they give your app secure, persistent authentication with minimal boilerplate.