OAuth 2.0 Fundamentals & Google Sign-In
OAuth 2.0 Fundamentals & Google Sign-In
OAuth 2.0 is the industry-standard authorization framework that allows a third-party application to obtain limited access to a user's account on another service — without ever seeing the user's password. In Flutter, it is the foundation of social sign-in flows such as Google Sign-In, GitHub, Facebook, and Apple. Understanding the protocol is essential before wiring up any SDK.
Core OAuth 2.0 Concepts
OAuth 2.0 defines four roles and two primary grant flows relevant to mobile apps:
- Resource Owner — the end user who owns the data (e.g., their Google profile).
- Client — your Flutter app requesting access.
- Authorization Server — the server that authenticates the user and issues tokens (e.g.,
accounts.google.com). - Resource Server — the API that holds the user's data (e.g.,
www.googleapis.com).
The Authorization Code Flow (Recommended for Mobile)
The Authorization Code Flow is the most secure grant type for mobile and server-side applications. It uses a short-lived authorization code that is exchanged for tokens on the back-end, keeping tokens off the device's network traffic. The steps are:
- App redirects user to the Authorization Server's login page.
- User authenticates and consents to requested scopes.
- Authorization Server redirects back to your app with a one-time authorization code.
- App (or its back-end) exchanges the code for an access token and refresh token via a POST request that includes the client secret.
- Access token is used to call the Resource Server on behalf of the user.
google_sign_in package handles this automatically on Android and iOS.The Implicit Flow (Legacy — Avoid)
The Implicit Flow was originally designed for single-page web apps. It skips the code-exchange step and returns the access token directly in the redirect URI fragment. Its problems on mobile include:
- Access token is exposed in the URL and device logs.
- No refresh token is issued, so sessions are short-lived.
- Vulnerable to token leakage via referrer headers or browser history.
The OAuth 2.0 Security Best Current Practice (BCP) explicitly deprecates the implicit flow. Always use the Authorization Code Flow with PKCE for Flutter apps.
Implementing Google Sign-In with google_sign_in
The google_sign_in package wraps the native Google Identity platform on both Android (Google Play Services) and iOS (GoogleSignIn SDK). Add it to your project:
pubspec.yaml — Add Dependencies
dependencies:
flutter:
sdk: flutter
google_sign_in: ^6.2.1
firebase_core: ^2.27.0
firebase_auth: ^4.19.0
On Android, register your app in the Firebase Console, download google-services.json, and place it in android/app/. On iOS, download GoogleService-Info.plist, add it to the Xcode project, and register the reversed client ID as a URL scheme. These steps are one-time configuration; the package then handles the OAuth dance for you.
Google Sign-In Service — Full Implementation
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
class GoogleAuthService {
final GoogleSignIn _googleSignIn = GoogleSignIn(
scopes: [
'email',
'profile',
// Add extra scopes here if needed, e.g. 'https://www.googleapis.com/auth/drive.readonly'
],
);
final FirebaseAuth _auth = FirebaseAuth.instance;
/// Initiates the Google Sign-In flow and links the credential to Firebase Auth.
/// Returns the signed-in [User] on success, or null if the user cancelled.
Future<User?> signInWithGoogle() async {
try {
// Step 1: Show the Google account picker (Authorization Code Flow via PKCE)
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
if (googleUser == null) {
// User cancelled the sign-in dialog
return null;
}
// Step 2: Retrieve the OAuth tokens
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
// Step 3: Exchange for a Firebase credential
final OAuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
// Step 4: Sign in to Firebase with the Google credential
final UserCredential userCredential =
await _auth.signInWithCredential(credential);
return userCredential.user;
} catch (e) {
// Handle errors: network failure, sign-in cancelled, account disabled, etc.
rethrow;
}
}
/// Signs out from both Firebase and the Google account.
Future<void> signOut() async {
await Future.wait([
_auth.signOut(),
_googleSignIn.signOut(),
]);
}
}
Listening to Auth State Changes in the Widget Tree
After a successful sign-in, Firebase Auth broadcasts a stream of User? events. Subscribe to FirebaseAuth.instance.authStateChanges() in your root widget to reactively navigate the user to the correct screen:
Auth Gate — Reactive Navigation
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) {
// While the stream is initialising, show a loading indicator
if (snapshot.connectionState == ConnectionState.waiting) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
// If a User is present, go to the home screen
if (snapshot.hasData) {
return const HomeScreen();
}
// Otherwise show the sign-in screen
return const SignInScreen();
},
);
}
}
Security Considerations
- Never request scopes you do not use. Overly broad scopes are a privacy red flag and will fail Google's OAuth verification for published apps.
- Validate the ID token server-side if you maintain your own back-end. Firebase Auth does this automatically when you call
signInWithCredential. - Store tokens securely. Firebase Auth persists the session in the platform's secure storage (Keychain on iOS, EncryptedSharedPreferences on Android). Do not cache raw access tokens yourself.
- Handle token expiry. The access token typically expires in one hour. The
google_sign_inpackage can silently refresh it viagoogleUser.authenticationif a valid refresh token exists.
_googleSignIn.signInSilently() on app start to restore a previous session without showing the account picker. This gives returning users a seamless experience.Summary
OAuth 2.0 separates authentication (who you are) from authorization (what you are allowed to do). The Authorization Code Flow with PKCE is the correct choice for mobile apps; the implicit flow is deprecated and insecure. The google_sign_in package implements this flow natively, and FirebaseAuth.signInWithCredential bridges the resulting Google credential into Firebase's user management system. Listening to authStateChanges() gives your app a reactive, always-consistent view of the current user.