Lesson 48 of 51 advanced

Social Login

Sign In with Google & Apple

Open interactive version (quiz + challenge)

Real-world analogy

Social login is like using your library card at a partner bookstore. Instead of creating a brand new account at the bookstore (your app), you show your library card (Google/Apple account). The bookstore calls the library to confirm you are real (token verification), then gives you a bookstore loyalty card (your app's tokens). One less password to remember!

What is it?

Social login lets users sign in with their Google or Apple accounts instead of creating a new username and password. In team_mvp_kit, the flow is: user taps Google/Apple button -> Firebase Auth authenticates them -> app gets a Firebase ID token -> sends it to the backend -> backend verifies and returns app-specific JWT tokens -> tokens are saved locally.

Real-world relevance

team_mvp_kit's FirebaseSocialAuthService orchestrates the entire social login flow. Google Sign-In and Apple Sign-In are the two most popular providers. Apple Sign-In is mandatory on iOS if you offer any other social login. The backend token exchange pattern lets you use Firebase for the easy social auth part while keeping your own user database and token system.

Key points

Code example

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

// FirebaseSocialAuthService (team_mvp_kit pattern)
@lazySingleton
class FirebaseSocialAuthService {
  final FirebaseAuth _auth;
  final GoogleSignIn _googleSignIn;
  final ApiService _api;
  final TokenStorage _tokenStorage;

  FirebaseSocialAuthService(
    this._auth,
    this._googleSignIn,
    this._api,
    this._tokenStorage,
  );

  // Google Sign-In
  Future<AppUser> signInWithGoogle() async {
    // Step 1: Google account picker
    final googleUser = await _googleSignIn.signIn();
    if (googleUser == null) {
      throw AuthCancelledException();
    }

    // Step 2: Get Google auth tokens
    final googleAuth = await googleUser.authentication;

    // Step 3: Create Firebase credential
    final credential = GoogleAuthProvider.credential(
      accessToken: googleAuth.accessToken,
      idToken: googleAuth.idToken,
    );

    // Step 4: Sign in to Firebase
    final userCredential =
        await _auth.signInWithCredential(credential);

    // Step 5: Exchange Firebase token with backend
    return _exchangeWithBackend(
      userCredential.user!,
      'google',
    );
  }

  // Apple Sign-In
  Future<AppUser> signInWithApple() async {
    // Step 1: Apple authorization
    final appleCredential =
        await SignInWithApple.getAppleIDCredential(
      scopes: [
        AppleIDAuthorizationScopes.email,
        AppleIDAuthorizationScopes.fullName,
      ],
    );

    // Step 2: Create Firebase credential
    final oauthCredential =
        OAuthProvider('apple.com').credential(
      idToken: appleCredential.identityToken,
      accessToken: appleCredential.authorizationCode,
    );

    // Step 3: Sign in to Firebase
    final userCredential =
        await _auth.signInWithCredential(oauthCredential);

    // Step 4: Exchange with backend
    return _exchangeWithBackend(
      userCredential.user!,
      'apple',
    );
  }

  // Exchange Firebase ID token for app tokens
  Future<AppUser> _exchangeWithBackend(
    User firebaseUser,
    String provider,
  ) async {
    final idToken = await firebaseUser.getIdToken();

    final response = await _api.post('/auth/social', data: {
      'firebase_token': idToken,
      'provider': provider,
    });

    // Save app tokens
    final tokens = TokensDto.fromJson(response['tokens']);
    await _tokenStorage.saveTokens(tokens);

    // Return app user
    return AppUser.fromJson(response['user']);
  }

  // Sign out from everything
  Future<void> signOut() async {
    await _googleSignIn.signOut();
    await _auth.signOut();
    await _tokenStorage.clearTokens();
  }
}

Line-by-line walkthrough

  1. 1. Import google_sign_in package for Google authentication
  2. 2. Import sign_in_with_apple for Apple authentication
  3. 3. Import firebase_auth for Firebase credential handling
  4. 4. Import injectable for dependency injection
  5. 5.
  6. 6. Comment: This is the team_mvp_kit pattern for social auth
  7. 7. @lazySingleton registers one instance for the app
  8. 8. Class declaration for FirebaseSocialAuthService
  9. 9. Private field for FirebaseAuth instance
  10. 10. Private field for GoogleSignIn instance
  11. 11. Private field for ApiService to call the backend
  12. 12. Private field for TokenStorage to save tokens
  13. 13.
  14. 14. Constructor receives all four dependencies via DI
  15. 15. Closing the constructor
  16. 16.
  17. 17. Comment: Google Sign-In method
  18. 18. Method returns an AppUser (your domain entity)
  19. 19. Comment: Step 1
  20. 20. Trigger the Google account picker UI
  21. 21. If user cancelled (returned null), throw exception
  22. 22. The AuthCancelledException signals the BLoC to handle it
  23. 23. Closing the cancel check
  24. 24.
  25. 25. Comment: Step 2
  26. 26. Get the Google auth tokens from the selected account
  27. 27.
  28. 28. Comment: Step 3
  29. 29. Create a Firebase credential from Google tokens
  30. 30. Pass the Google access token
  31. 31. Pass the Google ID token
  32. 32. Closing the credential creation
  33. 33.
  34. 34. Comment: Step 4
  35. 35. Sign in to Firebase with the Google credential
  36. 36. Closing the Firebase sign-in
  37. 37.
  38. 38. Comment: Step 5
  39. 39. Exchange the Firebase token with the backend
  40. 40. Pass the Firebase user and provider name
  41. 41. Closing signInWithGoogle
  42. 42.
  43. 43. Comment: Apple Sign-In method
  44. 44. Method returns an AppUser
  45. 45. Comment: Step 1
  46. 46. Request Apple authorization with scopes
  47. 47. Request email scope
  48. 48. Request full name scope
  49. 49. Closing the scopes and credential request
  50. 50.
  51. 51. Comment: Step 2
  52. 52. Create a Firebase OAuth credential for Apple
  53. 53. Set the Apple identity token as idToken
  54. 54. Set the authorization code as accessToken
  55. 55. Closing the credential creation
  56. 56.
  57. 57. Comment: Step 3
  58. 58. Sign in to Firebase with the Apple credential
  59. 59. Closing the Firebase sign-in
  60. 60.
  61. 61. Comment: Step 4
  62. 62. Exchange with backend (same flow as Google)
  63. 63. Pass the Firebase user and 'apple' as provider
  64. 64. Closing signInWithApple
  65. 65.
  66. 66. Comment: Private method to exchange Firebase token for app tokens
  67. 67. Method takes a Firebase User and provider string
  68. 68. Get the Firebase ID token from the user
  69. 69.
  70. 70. Send the ID token to the backend /auth/social endpoint
  71. 71. Include the Firebase token in the request body
  72. 72. Include which provider was used (google or apple)
  73. 73. Closing the API call
  74. 74.
  75. 75. Comment: Save the app tokens received from backend
  76. 76. Parse the tokens from the response
  77. 77. Save them to encrypted storage
  78. 78.
  79. 79. Comment: Return the app user
  80. 80. Parse and return the user data from the response
  81. 81. Closing _exchangeWithBackend
  82. 82.
  83. 83. Comment: Sign out from all providers
  84. 84. signOut method clears everything
  85. 85. Sign out from Google (clears cached account selection)
  86. 86. Sign out from Firebase
  87. 87. Clear app-specific tokens from storage
  88. 88. Closing signOut and FirebaseSocialAuthService

Spot the bug

Future<UserCredential> signInWithGoogle() async {
  final googleAuth = await GoogleSignIn().signIn();
  final credential = GoogleAuthProvider.credential(
    accessToken: googleAuth.accessToken,
    idToken: googleAuth.idToken,
  );
  return FirebaseAuth.instance.signInWithCredential(credential);
}
Need a hint?
GoogleSignIn().signIn() returns a GoogleSignInAccount, not the auth tokens directly. There is a missing step...
Show answer
GoogleSignIn().signIn() returns a GoogleSignInAccount (not GoogleSignInAuthentication). You need to call .authentication on the account to get the tokens. Also, signIn() can return null if cancelled. Fix: add a null check, then call 'final googleAuth = await googleUser.authentication;' before creating the credential.

Explain like I'm 5

Imagine you want to join a new sports club. Instead of filling out a long form with your name, address, and photo, you say 'I already have a library card!' The sports club calls the library to check if you are real. The library says 'Yep, that is Alice, age 10, she is a real person!' Then the sports club gives you their own membership card. That is social login -- using an account you already have (Google or Apple) to join a new app without creating a new password!

Fun fact

Apple made Sign in with Apple mandatory in 2020 for any iOS app that offers third-party login. This was partly for privacy -- Apple generates a unique relay email address for each app, so apps never see your real email. Google Sign-In, on the other hand, has been available since 2014 and is used by over 2 million apps!

Hands-on challenge

Implement a complete Google Sign-In flow: create a FirebaseSocialAuthService, handle the sign-in, get the Firebase ID token, and simulate a backend token exchange. Handle the case where the user cancels. Add an Apple Sign-In method that is only shown on iOS.

More resources

Open interactive version (quiz + challenge) ← Back to course: Flutter & Dart