Sitemap
Lia

Lia

Studying code…Flutter Social Login: Implementing Google & Apple Sign-In with Firebase

9 min readDec 1, 2025

--

How I implemented Google and Apple login for my Flutter app using Firebase Authentication.

Press enter or click to view image in full size

📦 Understanding the Packages Used in Flutter Authentication

import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
import 'package:crypto/crypto.dart';
import 'dart:convert';

Before writing any authentication logic, the first step in a Flutter project is to import the necessary packages. Each package serves a specific purpose in enabling login features such as Google Sign-In or Sign in with Apple. Here is a simple overview of what each one does.

1. firebase_auth

This package provides all the tools needed for handling user authentication in Firebase.
It supports multiple login methods, including email/password, Google, Apple, and more. In this project, it is used to connect the social login credentials to Firebase so the user can be authenticated securely.

2. google_sign_in

This package opens the Google account selection window and lets users sign in with their Google account.
It handles the entire Google login flow and retrieves the necessary authentication tokens that Firebase will use.

3. sign_in_with_apple

This package enables “Sign in with Apple” on iOS and macOS.
It handles the Apple login interface, retrieves the user’s Apple identity token, and works together with Firebase to complete the authentication process.

4. crypto

The crypto package provides hashing functions such as SHA-256.
In social login, especially Apple Sign-In, hashing is used to create a secure nonce, which protects the authentication process from replay attacks.

5. dart:convert

This is a built-in Dart library used for encoding and decoding data.
For example, it can convert a string into a UTF-8 byte array before hashing it with the crypto package.

📌 Class + GoogleSignIn Setup

class AuthService {
Future<UserCredential?> signInWithGoogle() async {
try {
final googleSignIn = GoogleSignIn(
scopes: ['email'],
);

1. Defining the AuthService Class

The code begins by creating a class named AuthService:

class AuthService 

This class serves as a container for authentication-related methods.
Organizing login functions inside a dedicated class helps keep the project structure clean, reusable, and easy to maintain. Any screen in the app can use the class by creating an instance such as:

final authService = AuthService();

This allows different parts of the application to call the same login methods without duplicating code.

2. The signInWithGoogle() Method

Inside the class, the method signInWithGoogle() handles the Google login flow. It returns a Future<UserCredential?>, meaning:

  • The method runs asynchronously because it communicates with external services (Google and Firebase).
  • When the login completes, it returns either a UserCredential object (containing the user’s authentication data) or null if the login was not successful.

The try–catch block is used to prevent the app from crashing if something unexpected happens during the login process.

3. Creating the GoogleSignIn Object

The first step in the method is to create a GoogleSignIn object:

final googleSignIn = GoogleSignIn(
scopes: ['email'],
);

This object manages the Google login flow. It handles tasks such as:

  • Displaying the Google account selector
  • Managing the authentication request
  • Returning the user’s login information

4. Understanding scopes: ['email']

The scopes parameter specifies which permissions the app is requesting when the user signs in.

By including:

scopes: ['email']

the app asks Google for permission to access the user’s email address.

This does not limit the returned data. Even with only the email scope, Google still provides:

  • basic profile information
  • the user’s Google ID
  • the display name
  • the profile picture (if available)

The email scope simply ensures the email value is included, which is important for most authentication systems.

🔹 Opening the Google Login Window

final GoogleSignInAccount? user = await googleSignIn.signIn();
if (user == null) return null;

Calling signIn() opens the Google login popup and allows the user to select an account.
The method returns a GoogleSignInAccount object containing the chosen account’s basic information. If the user closes the popup or cancels the login process, the returned value is null, and the login process stops safely.

🔹 Retrieving Authentication Tokens

final GoogleSignInAuthentication auth = await user.authentication;

After selecting an account, the next step is to fetch the authentication tokens.
The returned GoogleSignInAuthentication object contains two important values:

  • idToken — proof of the user’s identity
  • accessToken — limited access token for Google services

These tokens confirm that the user has successfully logged in through Google and allow Firebase to validate the sign-in.

🔹 Creating a Firebase Credential

final credential = GoogleAuthProvider.credential(
idToken: auth.idToken,
accessToken: auth.accessToken,
);

Firebase uses these tokens to generate a credential that represents a valid Google login. This credential is later passed to Firebase Authentication to complete the sign-in process.

📌 Completing the Google Sign-In Process with Firebase

After creating a Firebase credential using the tokens from Google, the final step is to pass that credential to Firebase Authentication. This completes the login process and returns the authenticated user information.

return await FirebaseAuth.instance.signInWithCredential(credential);
} catch (e) {
print("Google login error: $e");
return null;
}

🔹 Signing In with Firebase

The signInWithCredential() method is part of the FirebaseAuth class. It takes the previously created credential (containing Google’s authentication tokens) and sends it to Firebase’s servers for verification.

Firebase checks that the tokens are valid, confirms that they came from Google, and then signs the user in. When the process succeeds, the method returns a UserCredential object, which includes:

  • the authenticated user’s unique ID
  • their email address (if available)
  • basic profile information
  • details about how the user signed in

Because this step involves communicating with Firebase’s servers, it is awaited and may take some time to complete.

🔹 Error Handling

The entire login operation is wrapped in a try–catch block. If an error occurs at any point—for example, a network issue or invalid token—the catch block prints an error message to the console and safely returns null. This prevents the application from crashing and ensures that failures are handled gracefully.

📌 Implementing the First Steps of Apple Sign-In in Flutter

Apple Sign-In follows a similar overall structure to Google Sign-In, but it includes an additional security requirement involving a nonce. A nonce ensures that each login attempt is unique and helps protect the authentication process from replay attacks. Below is the initial portion of the Apple login method used in many Flutter projects.

Future<UserCredential?> signInWithApple() async {
try {
final rawNonce = _generateNonce();
final nonce = _sha256ofString(rawNonce);
final appleCredential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
nonce: nonce,
);
final oauth = OAuthProvider("apple.com").credential(
idToken: appleCredential.identityToken,
rawNonce: rawNonce,
);

🔹 Generating the Nonce

The method begins by creating a random string called a raw nonce:

final rawNonce = _generateNonce();

A nonce (short for “number used once”) is a value that should be unique every time the user attempts to sign in. Apple requires this to ensure that the login request cannot be reused or replicated by an attacker.

Get Lia’s stories in your inbox

Join Medium for free to get updates from this writer.

Since the raw nonce must remain private, it is hashed before being sent to Apple. Hashing transforms data into a one-way, irreversible form — ideal for secure transmission.

final nonce = _sha256ofString(rawNonce);

The resulting nonce is a SHA-256 hash of the original random value.

🔹 Requesting Apple Credentials

Next, the code requests Apple ID information using the SignInWithApple package:

final appleCredential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
nonce: nonce,
);

Here’s what happens in this step:

  • The system displays the Apple Sign-In interface.
  • The email and fullName scopes request permission to access the user’s email address and full name.
  • The hashed nonce is sent along with the request for security verification.
  • Apple returns an object containing an identity token and any profile information that the user allows the app to access.

The returned credentials will later be used to create a Firebase-compatible authentication credential.

🔹 Creating the Firebase OAuth Credential

Once the Apple ID credentials are returned, the next step is to convert them into a format that Firebase Authentication can understand. Apple provides an identity token, which confirms the user’s identity, and earlier the app generated a raw nonce for security. Firebase requires both pieces to verify that the login request is genuine.

final oauth = OAuthProvider("apple.com").credential(
idToken: appleCredential.identityToken,
rawNonce: rawNonce,
);

This line performs several important tasks:

1. Initializing the OAuth Provider

OAuthProvider("apple.com")

Firebase uses an OAuth provider to handle third-party authentication.
By specifying "apple.com", the code tells Firebase that this credential belongs to Apple Sign-In.

2. Passing the Identity Token

idToken: appleCredential.identityToken

The identity token is a signed token issued by Apple.
It confirms who the user is and that the login step was completed successfully.

Firebase later validates this token to confirm that it originated from Apple.

3. Passing the Raw Nonce

rawNonce: rawNonce

Firebase uses the raw nonce (the un-hashed version) to check whether:

SHA256(rawNonce) == nonce included in Apple’s identity token

If they match, it confirms that:

  • the login request is legitimate
  • the token was not reused
  • the token was not intercepted or tampered with

This protects the authentication flow from replay attacks and ensures that each Apple login attempt is unique.

4. Preparing for Firebase Authentication

The resulting oauth credential is now ready to be passed into Firebase Authentication.
It contains all the verified information Firebase needs to sign the user in securely.

📌 Completing the Apple Sign-In Process with Firebase

After obtaining the Apple ID credential and preparing the OAuth credential, the final step is to pass that credential to Firebase Authentication. This completes the sign-in process and returns the authenticated user information.

return await FirebaseAuth.instance.signInWithCredential(oauth);
} catch (e) {
print("Apple login error: $e");
return null;
}

🔹 Signing In With Firebase

The call to signInWithCredential() sends the Apple OAuth credential—containing the identity token and nonce—to Firebase’s servers for verification. Firebase checks that:

  • the identity token was issued by Apple,
  • the token is valid and unexpired,
  • the hashed nonce inside the token matches the raw nonce provided earlier.

If all verification steps succeed, Firebase completes the authentication process and returns a UserCredential object. This object contains the authenticated user’s unique ID and additional information about how the user signed in.

Because this step involves network communication with Firebase, it is awaited and may take a moment to complete.

🔹 Handling Errors Gracefully

The entire login operation is wrapped in a try–catch block. If an error occurs at any point—such as token verification failure, network issues, or user cancellation—the catch block prints an error message to the console and returns null.

catch (e) {
print("Apple login error: $e");
return null;
}

This ensures that the application handles sign-in failures safely without crashing.

📌 Generating a Secure Nonce for Apple Sign-In in Flutter

The _generateNonce() method below creates this random string (nonce).

String _generateNonce([int length = 32]) {
const charset =
'0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._';
return List.generate(
length,
(index) => charset[(DateTime.now().microsecond + index) % charset.length],
).join();
}

🔹 Creating the Raw Nonce

The method returns a random string that can be used as the raw nonce for Apple authentication. It is defined as a private function (indicated by the leading underscore) and returns a String. The function takes an optional length parameter, which defaults to 32. This means that if no value is provided, the method will always generate a 32-character nonce.

String _generateNonce([int length = 32])

🔹 Defining the Character Set

A constant charset string defines which characters may appear in the nonce:

const charset =
'0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._';

This includes uppercase letters, lowercase letters, numbers, and symbols commonly allowed in cryptographically random strings.

🔹 Generating Random Characters

The key part of the method uses List.generate() to build a list containing the desired number of characters:

List.generate(
length,
(index) => ...
)

List.generate creates a list with length elements. For each position in the list, the function uses the current time in microseconds combined with the loop index to produce variation:

(DateTime.now().microsecond + index)

Because this value may exceed the bounds of the character set, a modulo operation ensures the result always falls within the valid index range:

% charset.length

The final value is used to select a character from the charset string:

charset[( ... )]

This produces a pseudo-random character for each position in the list.

🔹 Returning the Final Nonce

Finally, the list of characters is combined into a single string using:

.join()

The result is a randomized string that can safely be used as a nonce in the Apple authentication flow.

📌 Hashing the Nonce with SHA-256

After generating the raw nonce string used for Apple Sign-In, the next step is to hash it securely before sending it to Apple. Hashing transforms the raw nonce into an irreversible, fixed-length output. Apple uses this hashed value to verify the authenticity of the sign-in request and protect the login flow from replay attacks.

The following method performs the required SHA-256 hashing:

String _sha256ofString(String input) {
final bytes = utf8.encode(input);
final digest = sha256.convert(bytes);
return digest.toString();
}

🔹 Encoding the Input

Before applying the hash algorithm, the string must be converted into bytes:

final bytes = utf8.encode(input);

SHA-256 operates on raw bytes rather than characters. Using UTF-8 encoding ensures that the string is consistently transformed into a predictable sequence of bytes, regardless of the device or platform.

🔹 Applying the SHA-256 Hash

The next step applies the SHA-256 algorithm from the crypto package:

final digest = sha256.convert(bytes);

The result, called a digest, is a hashed representation of the original data.
A SHA-256 digest:

  • is always the same length
  • cannot be reversed to reveal the original input
  • changes completely even if the input changes slightly

These properties make it ideal for validating authentication flows.

🔹 Returning the Hashed Output

Finally, the digest is converted into a readable hexadecimal string:

return digest.toString();

This string is the hashed nonce that will be sent to Apple as part of the authentication request.
Later, Firebase verifies that the hashed nonce inside Apple’s identity token matches the original raw nonce, ensuring the sign-in attempt is legitimate.

--

--