Abhay Talreja
About MeProjectsCoursesExperienceToolsResourcesBlog

© 2025 Abhay Talreja. All rights reserved.

Terms of ServicePrivacy PolicyLegal NoticeCookie PolicyXML Sitemap
    Back to all articles
    Development

    Flutter + Supabase Authentication: Complete Guide to Sign in with Google and Apple

    A comprehensive step-by-step guide to implementing Google and Apple Sign-In in your Flutter app using Supabase. Learn how to configure OAuth providers, handle platform-specific setup, and create a seamless authentication experience.

    December 7, 2025
    15 min read
    Flutter
    Supabase
    Authentication
    Google Sign-In
    Apple Sign-In
    OAuth
    iOS Development
    Android Development
    Mobile Development
    Flutter + Supabase Authentication: Complete Guide to Sign in with Google and Apple

    Flutter + Supabase Authentication: Complete Guide to Sign in with Google and Apple

    TL;DR

    • Supabase provides a simple way to add OAuth authentication to your Flutter app
    • Google Sign-In works on both iOS and Android
    • Apple Sign-In is required for iOS apps that offer social login (App Store guideline)
    • This guide covers Google Cloud Console, Apple Developer setup, Supabase configuration, and Flutter implementation
    • Total setup time: ~1-2 hours

    Social authentication has become the standard for mobile apps. Users expect to sign in with their existing Google or Apple accounts rather than creating yet another username and password. In this guide, I'll walk you through implementing both providers using Supabase as your backend.

    Why Supabase for Authentication?

    Before diving into the implementation, let's understand why Supabase is an excellent choice:

    1. Open Source - No vendor lock-in, you can self-host if needed
    2. Generous Free Tier - 50,000 monthly active users for free
    3. Built-in User Management - Database tables, row-level security, and user metadata
    4. Multiple Auth Providers - Google, Apple, GitHub, Twitter, and more
    5. Flutter SDK - First-class support with type-safe client

    Prerequisites

    Before starting, ensure you have:

    • A Flutter project set up (Flutter 3.0+)
    • A Supabase account (free at supabase.com)
    • Access to Google Cloud Console
    • Access to Apple Developer account (for Apple Sign-In)
    • Your app's bundle ID/package name ready

    Part 1: Supabase Project Setup

    Step 1.1: Create a Supabase Project

    1. Go to Supabase Dashboard
    2. Click New Project
    3. Fill in:
      • Name: Your project name (e.g., "MyApp")
      • Database Password: Generate a strong password (save this!)
      • Region: Choose closest to your users
    4. Click Create new project
    5. Wait 2-3 minutes for setup to complete

    Step 1.2: Get Your Project Credentials

    Once your project is ready:

    1. Go to Project Settings (gear icon)
    2. Click API in the sidebar
    3. Note down:
      • Project URL: https://xxxxxxxxxxxx.supabase.co
      • anon public key: eyJhbGciOiJIUzI1NiIs...

    Security Note: The anon key is safe to include in your app. Row Level Security (RLS) policies protect your data.

    Step 1.3: Configure Authentication Settings

    1. Go to Authentication in the sidebar
    2. Click Providers
    3. You'll see a list of available providers - we'll configure Google and Apple next

    Part 2: Google Sign-In Setup

    Step 2.1: Create Google Cloud Project

    1. Go to Google Cloud Console
    2. Click the project dropdown at the top → New Project
    3. Name your project and click Create
    4. Select your new project from the dropdown

    Step 2.2: Configure OAuth Consent Screen

    1. Go to APIs & Services → OAuth consent screen
    2. Select External (unless you have Google Workspace)
    3. Click Create
    4. Fill in required fields:
      • App name: Your app name
      • User support email: Your email
      • Developer contact information: Your email
    5. Click Save and Continue
    6. Skip Scopes for now → Click Save and Continue
    7. Add test users if needed → Click Save and Continue
    8. Review and click Back to Dashboard

    Step 2.3: Create OAuth Credentials

    For Web (Required for Supabase)

    1. Go to APIs & Services → Credentials
    2. Click Create Credentials → OAuth client ID
    3. Select Web application
    4. Name it (e.g., "Supabase Web Client")
    5. Under Authorized redirect URIs, add:
      https://YOUR_PROJECT_REF.supabase.co/auth/v1/callback
      
      (Replace YOUR_PROJECT_REF with your Supabase project reference)
    6. Click Create
    7. Copy the Client ID and Client Secret

    For iOS

    1. Click Create Credentials → OAuth client ID
    2. Select iOS
    3. Enter your Bundle ID (e.g., com.yourcompany.yourapp)
    4. Click Create
    5. Copy the iOS Client ID (you'll need this in your Flutter app)

    For Android

    1. Click Create Credentials → OAuth client ID
    2. Select Android
    3. Enter:
      • Package name: Your Android package (e.g., com.yourcompany.yourapp)
      • SHA-1 certificate fingerprint: Get this by running:
        # For debug keystore keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android # For release keystore keytool -list -v -keystore /path/to/release.keystore -alias your-alias
    4. Click Create

    Step 2.4: Configure Google in Supabase

    1. Go to your Supabase Dashboard → Authentication → Providers
    2. Find Google and click to expand
    3. Toggle Enable Sign in with Google
    4. Enter:
      • Client ID: Web Client ID from Step 2.3
      • Client Secret: Web Client Secret from Step 2.3
    5. Click Save

    Part 3: Apple Sign-In Setup

    Important: Apple Sign-In is required by App Store guidelines if your iOS app offers any other social login option.

    Step 3.1: Configure App ID

    1. Go to Apple Developer Portal
    2. Navigate to Certificates, Identifiers & Profiles
    3. Click Identifiers → Select your App ID (or create one)
    4. Scroll to Sign In with Apple
    5. Check the checkbox to enable
    6. Click Edit next to Sign In with Apple
    7. Select Enable as a primary App ID
    8. Click Save

    Step 3.2: Create a Service ID

    1. Go to Identifiers → Click + to add new
    2. Select Services IDs → Click Continue
    3. Fill in:
      • Description: Your app name (e.g., "MyApp Web")
      • Identifier: Reverse domain (e.g., com.yourcompany.yourapp.web)
    4. Click Continue → Register
    5. Click on your newly created Service ID
    6. Check Sign In with Apple → Click Configure
    7. Set:
      • Primary App ID: Select your app
      • Domains: YOUR_PROJECT_REF.supabase.co
      • Return URLs: https://YOUR_PROJECT_REF.supabase.co/auth/v1/callback
    8. Click Save → Continue → Save

    Step 3.3: Create a Sign-In Key

    1. Go to Keys → Click + to add new
    2. Enter a Key Name (e.g., "Sign In with Apple Key")
    3. Check Sign In with Apple → Click Configure
    4. Select your Primary App ID
    5. Click Save → Continue → Register
    6. Download the key file (.p8) - you can only download it once!
    7. Note the Key ID

    Step 3.4: Get Your Team ID

    1. Go to Apple Developer Account
    2. Scroll down to Membership details
    3. Copy your Team ID

    Step 3.5: Configure Apple in Supabase

    1. Go to your Supabase Dashboard → Authentication → Providers
    2. Find Apple and click to expand
    3. Toggle Enable Sign in with Apple
    4. Enter:
      • Service ID: The Service ID from Step 3.2 (e.g., com.yourcompany.yourapp.web)
      • Secret Key: Open the .p8 file in a text editor and paste the entire contents
      • Key ID: From Step 3.3
      • Team ID: From Step 3.4
    5. Click Save

    Part 4: Flutter Implementation

    Step 4.1: Add Dependencies

    Update your pubspec.yaml:

    dependencies: supabase_flutter: ^2.3.0 google_sign_in: ^6.2.1 sign_in_with_apple: ^6.1.0 crypto: ^3.0.3 # For generating nonce

    Run:

    flutter pub get

    Step 4.2: iOS Configuration

    Info.plist for Google Sign-In

    Add to ios/Runner/Info.plist:

    <key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleTypeRole</key> <string>Editor</string> <key>CFBundleURLSchemes</key> <array> <!-- Google Sign-In reversed client ID --> <string>com.googleusercontent.apps.YOUR_IOS_CLIENT_ID</string> </array> </dict> </array>

    Replace YOUR_IOS_CLIENT_ID with your iOS Client ID from Google Cloud Console (reversed format).

    Capabilities for Apple Sign-In

    1. Open ios/Runner.xcworkspace in Xcode
    2. Select your target → Signing & Capabilities
    3. Click + Capability → Add Sign In with Apple

    Step 4.3: Android Configuration

    No special configuration needed for Google Sign-In if you've added the SHA-1 fingerprint in Google Cloud Console.

    For release builds, ensure you add the release keystore SHA-1 as well.

    Step 4.4: Initialize Supabase

    In your main.dart:

    import 'package:flutter/material.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); await Supabase.initialize( url: 'https://YOUR_PROJECT_REF.supabase.co', anonKey: 'YOUR_ANON_KEY', ); runApp(const MyApp()); } // Global accessor for Supabase client final supabase = Supabase.instance.client;

    Step 4.5: Create Authentication Service

    Create lib/core/services/auth_service.dart:

    import 'dart:convert'; import 'dart:io'; import 'dart:math'; import 'package:crypto/crypto.dart'; import 'package:flutter/foundation.dart'; import 'package:google_sign_in/google_sign_in.dart'; import 'package:sign_in_with_apple/sign_in_with_apple.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; class AuthService { final SupabaseClient _supabase; AuthService(this._supabase); /// Get current user User? get currentUser => _supabase.auth.currentUser; /// Check if user is logged in bool get isLoggedIn => currentUser != null; /// Stream of auth state changes Stream<AuthState> get authStateChanges => _supabase.auth.onAuthStateChange; /// Sign in with Google Future<AuthResponse> signInWithGoogle() async { // Web client ID from Google Cloud Console const webClientId = 'YOUR_WEB_CLIENT_ID.apps.googleusercontent.com'; // iOS client ID from Google Cloud Console const iosClientId = 'YOUR_IOS_CLIENT_ID.apps.googleusercontent.com'; final GoogleSignIn googleSignIn = GoogleSignIn( clientId: Platform.isIOS ? iosClientId : null, serverClientId: webClientId, ); final googleUser = await googleSignIn.signIn(); if (googleUser == null) { throw AuthException('Google sign in was cancelled'); } final googleAuth = await googleUser.authentication; final accessToken = googleAuth.accessToken; final idToken = googleAuth.idToken; if (accessToken == null) { throw AuthException('No access token from Google'); } if (idToken == null) { throw AuthException('No ID token from Google'); } return _supabase.auth.signInWithIdToken( provider: OAuthProvider.google, idToken: idToken, accessToken: accessToken, ); } /// Sign in with Apple (iOS only) Future<AuthResponse> signInWithApple() async { // Generate a random nonce final rawNonce = _generateRandomString(32); final hashedNonce = sha256.convert(utf8.encode(rawNonce)).toString(); final credential = await SignInWithApple.getAppleIDCredential( scopes: [ AppleIDAuthorizationScopes.email, AppleIDAuthorizationScopes.fullName, ], nonce: hashedNonce, ); final idToken = credential.identityToken; if (idToken == null) { throw AuthException('No ID token from Apple'); } return _supabase.auth.signInWithIdToken( provider: OAuthProvider.apple, idToken: idToken, nonce: rawNonce, ); } /// Sign out Future<void> signOut() async { // Sign out from Google if signed in try { final GoogleSignIn googleSignIn = GoogleSignIn(); await googleSignIn.signOut(); } catch (e) { debugPrint('Google sign out error: $e'); } // Sign out from Supabase await _supabase.auth.signOut(); } /// Generate random string for nonce String _generateRandomString(int length) { const charset = '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._'; final random = Random.secure(); return List.generate(length, (_) => charset[random.nextInt(charset.length)]) .join(); } } /// Custom auth exception class AuthException implements Exception { final String message; AuthException(this.message); @override String toString() => message; }

    Step 4.6: Create Login Screen

    Create lib/features/auth/presentation/screens/login_screen.dart:

    import 'dart:io'; import 'package:flutter/material.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({super.key}); @override State<LoginScreen> createState() => _LoginScreenState(); } class _LoginScreenState extends State<LoginScreen> { final _authService = AuthService(Supabase.instance.client); bool _isLoading = false; String? _errorMessage; Future<void> _signInWithGoogle() async { setState(() { _isLoading = true; _errorMessage = null; }); try { await _authService.signInWithGoogle(); // Navigation will be handled by auth state listener } catch (e) { setState(() { _errorMessage = e.toString(); }); } finally { if (mounted) { setState(() { _isLoading = false; }); } } } Future<void> _signInWithApple() async { setState(() { _isLoading = true; _errorMessage = null; }); try { await _authService.signInWithApple(); // Navigation will be handled by auth state listener } catch (e) { setState(() { _errorMessage = e.toString(); }); } finally { if (mounted) { setState(() { _isLoading = false; }); } } } @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Padding( padding: const EdgeInsets.all(24.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // App logo or title const Text( 'Create Account', style: TextStyle( fontSize: 28, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), const SizedBox(height: 8), Text( 'Start your journey to better health', style: TextStyle( fontSize: 16, color: Colors.grey[600], ), textAlign: TextAlign.center, ), const SizedBox(height: 48), // Error message if (_errorMessage != null) Container( padding: const EdgeInsets.all(12), margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: Colors.red[50], borderRadius: BorderRadius.circular(8), ), child: Text( _errorMessage!, style: TextStyle(color: Colors.red[900]), textAlign: TextAlign.center, ), ), // Google Sign In Button _SocialSignInButton( onPressed: _isLoading ? null : _signInWithGoogle, icon: 'G', label: 'Sign in with Google', isLoading: _isLoading, ), const SizedBox(height: 16), // Apple Sign In Button (iOS only) if (Platform.isIOS) ...[ _SocialSignInButton( onPressed: _isLoading ? null : _signInWithApple, icon: '', label: 'Sign in with Apple', isLoading: _isLoading, isApple: true, ), const SizedBox(height: 24), ], // Terms of service Text( 'By signing in, you agree to our Terms of Service and Privacy Policy', style: TextStyle( fontSize: 12, color: Colors.grey[600], ), textAlign: TextAlign.center, ), ], ), ), ), ); } } class _SocialSignInButton extends StatelessWidget { final VoidCallback? onPressed; final String icon; final String label; final bool isLoading; final bool isApple; const _SocialSignInButton({ required this.onPressed, required this.icon, required this.label, this.isLoading = false, this.isApple = false, }); @override Widget build(BuildContext context) { return OutlinedButton( onPressed: onPressed, style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), side: BorderSide(color: Colors.grey[300]!), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30), ), ), child: isLoading ? const SizedBox( height: 24, width: 24, child: CircularProgressIndicator(strokeWidth: 2), ) : Row( mainAxisAlignment: MainAxisAlignment.center, children: [ if (isApple) const Icon(Icons.apple, size: 24) else Image.network( 'https://www.google.com/favicon.ico', height: 24, width: 24, errorBuilder: (_, __, ___) => const Text( 'G', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.blue, ), ), ), const SizedBox(width: 12), Text( label, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w500, ), ), ], ), ); } }

    Step 4.7: Handle Auth State Changes

    In your main app widget:

    import 'package:flutter/material.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'My App', home: const AuthGate(), ); } } class AuthGate extends StatelessWidget { const AuthGate({super.key}); @override Widget build(BuildContext context) { return StreamBuilder<AuthState>( stream: Supabase.instance.client.auth.onAuthStateChange, builder: (context, snapshot) { // Check for loading state if (snapshot.connectionState == ConnectionState.waiting) { return const Scaffold( body: Center(child: CircularProgressIndicator()), ); } // Check if user is logged in final session = snapshot.data?.session; if (session != null) { return const HomeScreen(); // Your main app screen } return const LoginScreen(); }, ); } }

    Part 5: Advanced Configuration

    Step 5.1: Store Additional User Data

    When a user signs in, you might want to store additional profile data:

    Future<void> _handleNewUser(User user) async { // Check if user already exists in your profiles table final existing = await Supabase.instance.client .from('profiles') .select() .eq('id', user.id) .maybeSingle(); if (existing == null) { // Create new profile await Supabase.instance.client.from('profiles').insert({ 'id': user.id, 'email': user.email, 'full_name': user.userMetadata?['full_name'] ?? user.userMetadata?['name'], 'avatar_url': user.userMetadata?['avatar_url'] ?? user.userMetadata?['picture'], 'created_at': DateTime.now().toIso8601String(), }); } }

    Step 5.2: Create Profiles Table (SQL)

    Run this in your Supabase SQL Editor:

    -- Create profiles table create table public.profiles ( id uuid references auth.users on delete cascade primary key, email text, full_name text, avatar_url text, created_at timestamp with time zone default timezone('utc'::text, now()), updated_at timestamp with time zone default timezone('utc'::text, now()) ); -- Enable Row Level Security alter table public.profiles enable row level security; -- Create policies create policy "Users can view their own profile" on public.profiles for select using (auth.uid() = id); create policy "Users can update their own profile" on public.profiles for update using (auth.uid() = id); -- Create trigger for automatic profile creation create or replace function public.handle_new_user() returns trigger as $$ begin insert into public.profiles (id, email, full_name, avatar_url) values ( new.id, new.email, new.raw_user_meta_data->>'full_name', new.raw_user_meta_data->>'avatar_url' ); return new; end; $$ language plpgsql security definer; create trigger on_auth_user_created after insert on auth.users for each row execute procedure public.handle_new_user();

    Step 5.3: Deep Link Configuration (Optional)

    For a smoother authentication flow, configure deep links:

    iOS (Info.plist)

    <key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleTypeRole</key> <string>Editor</string> <key>CFBundleURLSchemes</key> <array> <string>YOUR_BUNDLE_ID</string> </array> </dict> </array>

    Android (AndroidManifest.xml)

    <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="YOUR_PACKAGE_NAME" android:host="login-callback" /> </intent-filter>

    Part 6: Testing

    Step 6.1: Test Google Sign-In

    1. Run your app on a real device or emulator
    2. Tap "Sign in with Google"
    3. Complete the Google sign-in flow
    4. Verify user appears in Supabase Dashboard → Authentication → Users

    Step 6.2: Test Apple Sign-In

    1. Run on a real iOS device (Apple Sign-In doesn't work on simulator)
    2. Tap "Sign in with Apple"
    3. Complete Face ID/Touch ID or password
    4. Verify user appears in Supabase Dashboard

    Step 6.3: Verify in Supabase Dashboard

    1. Go to Authentication → Users
    2. You should see your test users with:
      • Email address
      • Provider (google, apple)
      • Created at timestamp

    Troubleshooting Common Issues

    "Invalid client ID" (Google)

    1. Verify Web Client ID matches in both Google Cloud Console and Supabase
    2. Ensure redirect URI is correctly configured: https://YOUR_PROJECT_REF.supabase.co/auth/v1/callback

    "Sign in with Apple failed"

    1. Verify Service ID matches in both Apple Developer and Supabase
    2. Check that the domain and redirect URL are correctly configured
    3. Ensure the .p8 key content is complete (including BEGIN/END lines)
    4. Verify Team ID and Key ID are correct

    "User not found" after sign-in

    1. Check that RLS policies allow the user to read their own data
    2. Verify the profiles table trigger is working
    3. Check Supabase logs for any errors

    Google Sign-In not working on Android

    1. Verify SHA-1 fingerprint is added to Google Cloud Console
    2. For release builds, add release keystore SHA-1
    3. Ensure package name matches exactly

    Security Best Practices

    1. Enable RLS - Always enable Row Level Security on tables
    2. Validate on Server - Don't trust client-side data
    3. Use HTTPS - Supabase handles this, but verify for any custom endpoints
    4. Secure API Keys - Never expose your service_role key
    5. Review Permissions - Regularly audit OAuth scopes and permissions

    Checklist Before Production

    • Google OAuth consent screen published (not in testing mode)
    • All OAuth credentials created for production
    • SHA-1 fingerprints added for release keystore
    • Apple Sign-In configured with correct bundle ID
    • Deep links configured for smooth auth flow
    • RLS policies enabled and tested
    • Error handling implemented for all auth flows
    • Loading states shown during authentication
    • Sign-out clears all auth state

    Conclusion

    Setting up Google and Apple Sign-In with Supabase provides a robust, secure authentication system for your Flutter app. The key benefits:

    1. User Experience - Familiar sign-in methods users trust
    2. Security - OAuth handles credentials, not your app
    3. Compliance - Apple Sign-In satisfies App Store requirements
    4. Scalability - Supabase handles millions of users
    5. Cost-Effective - Generous free tier for indie developers

    The initial setup takes some time, but once configured, you have a production-ready authentication system that scales with your app.


    Resources

    • Supabase Auth Documentation
    • Google Sign-In for Flutter
    • Sign in with Apple for Flutter
    • Apple Sign-In Guidelines
    • Supabase Flutter SDK

    Have questions? Found this helpful? Let me know on Twitter.

    Abhay Talreja

    Abhay Talreja

    Solution Architect & Technology Leader

    I help teams build scalable, maintainable software. Passionate about modern JavaScript, clean code, and sharing what I learn.

    More about me
    DevelopmentVibe-CodingReference

    Share this article

    Related Articles

    Complete Guide to Flutter In-App Purchases with RevenueCat: From App Store Rejection to Approval
    Development

    Complete Guide to Flutter In-App Purchases with RevenueCat: From App Store Rejection to Approval

    A comprehensive step-by-step guide to implementing in-app purchases in Flutter using RevenueCat. Learn how to configure App Store Connect, Google Play Console, and integrate RevenueCat SDK to handle subscriptions without server-side complexity.

    December 7, 2025•18 min read
    Building Scalable SaaS Applications Using AI: The Complete 2025 Guide
    Development

    Building Scalable SaaS Applications Using AI: The Complete 2025 Guide

    Master the art of building AI-powered SaaS applications that scale. Learn proven architectures, implementation patterns, and best practices from industry leaders who have built successful AI SaaS products.

    May 24, 2025•29 min read
    AI in Software Engineering: How Technical Architects Can Lead the Transformation
    Development

    AI in Software Engineering: How Technical Architects Can Lead the Transformation

    Discover how AI is revolutionizing software development and why technical architects should embrace, not fear, this transformation. Learn practical strategies to adapt, thrive, and lead in the AI-driven future of software engineering.

    May 24, 2025•11 min read