Mastering Firebase Authentication with Flutter
We all have accounts on various websites and apps. I am sure that you are familiar with this. But maybe you can ask this question:
What is Authentication?
Before diving into Firebase and its offerings, let’s first establish a fundamental understanding of authentication, which plays a critical role in modern application development.
Authentication is the process of verifying the identity of a user or system. It ensures that the individual accessing a service or application is who they claim to be.
Why do we need Authentication?
Understanding authentication naturally leads to the question: why is it so essential? Authentication forms the backbone of modern digital interactions, ensuring secure and personalized experiences while safeguarding sensitive information.
Authentication is critical for several reasons:
- Security: It protects sensitive data and resources from unauthorized access.
- Personalization: It enables applications to tailor the user experience based on the authenticated user’s preferences or history.
- Auditability: It ensures accountability by associating actions or transactions with specific users.
- Transferable: It provides the data and user experience transfer across devices.
Without authentication, applications could not securely manage user-specific information or provide differentiation access to features.
What is Firebase Authentication?
With the critical role of authentication established, you might wonder how to efficiently implement it in your applications. This is where Firebase Authentication comes into play.
You might feel you lack the knowledge or resources to build your own authentication system. Firebase Authentication is one of Firebase’s life-saving solutions. It simplifies the process of managing user identities and allows developers to implement secure authentication services in their applications. It supports a variety of authentication methods, including email and password, social sign-ins (Google, Apple, Facebook, etc.), anonymous sign-ins, and phone number verification.
Anonymous sign-in might confuse you when you hear it the very first time. Being anonymous doesn’t mean not signing in. It allows you to protect your data. Anonymous sign-in creates a user and a UID to follow. You can also link the anonymous user with other sign-in methods in the future. I will mention that below. Keep reading.
How can we add Firebase Authentication?
After understanding the capabilities of Firebase Authentication, let’s dive into how you can add this powerful tool to your Flutter application.
Step 1: Set up Firebase Project (if you do not have one)
- Go to the Firebase Console.
- Create a new project and register your Flutter app (iOS and Android for this article).
- Follow the instructions and download the
google-services.json
file for Android andGoogleService-Info.plist
for iOS. (If you plan to add Google Sign-in, you need to set it up on the console before downloading the files. Otherwise, you have to download the files again.)
Step 2: Initialize Firebase
With your Firebase project set up, the next step is to initialize Firebase within your Flutter application.
Add the required dependencies to your pubspec.yaml
file:
dependencies:
firebase_core: latest_version
firebase_auth: latest_version
In the main.dart
file, initialize Firebase:
import 'package:firebase_core/firebase_core.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
Create a class to manage authentication. Let’s call it FirebaseAuthenticationRepository
in the firebase_authentication_repository.dart
file:
import 'package:firebase_auth/firebase_auth.dart';
class FirebaseAuthenticationRepository {
final FirebaseAuth firebaseAuth = FirebaseAuth.instance;
// Add methods here...
}
This article does not cover the UI. You will see only the repository code for implementation.
You can read the official documentation here.
Step 3: Anonymous Authentication
Now that Firebase is initialized, we can proceed to implement various authentication methods, starting with anonymous authentication.
Allow users to access your app without required sign-up. It might be pretty helpful for some real-world use cases such as guest shopping carts, temporary user sessions, etc.
Before implementing an authentication, it is important to enable the authentication provider from the Firebase Console. It is pretty simple for Anonymous Authentication.
Go to the Authentication tab and choose the provider that you want to enable. (We will enable Anonymous for this example.)
When you click on the provider, it will show the second step for details and enabling. (Some providers need extra information. Keep reading.)
It’s time to implement it in the app. We will continue with the class we created above.
class FirebaseAuthenticationRepository {
final FirebaseAuth firebaseAuth = FirebaseAuth.instance;
// I suggest to create your own User model instead of using Firebase's model.
Future<User> signInAnonymously() async {
try{
final userCredential = await firebaseAuth.signInAnonymously();
final firebaseUser = userCredential.user;
if (firebaseUser == null) {
// You can create custom exceptions to handle errors better.
throw const AuthenticationException(message: 'Failed to sign in anonymously');
}
return firebaseUser;
} catch (e) {
// You can also handle the `FirebaseAuthException` and `AuthenticationException` specifically.
print(e);
rethrow;
}
}
}
You can read the official documentation here.
Step 4: Email and Password Authentication
While anonymous authentication is useful for temporary access, email and password authentication provide more traditional and secure approach. Let’s see how to implement this.
Like Anonymous Authentication, we need to enable Email/Password provider.
Now, let’s go back to the code. We need to add a method for creating users.
class FirebaseAuthenticationRepository{
final FirebaseAuth firebaseAuth = FirebaseAuth.instance;
/* ... */
Future<User> signUpWithEmailAndPassword(String email, String password) async {
try{
final userCredential = await firebaseAuth.createUserWithEmailAndPassword(email: email, password: password);
final firebaseUser = userCredential.user;
if (firebaseUser == null) {
throw const AuthenticationException(message: 'Failed to sign up with email and password');
}
return firebaseUser;
} catch (e) {
print(e);
rethrow;
}
}
}
We also need a method for signing in with email.
class FirebaseAuthenticationRepository{
final FirebaseAuth firebaseAuth = FirebaseAuth.instance;
/* ... */
Future<User> signInWithEmailAndPassword(String email, String password) async {
try{
final userCredential = await firebaseAuth.signInWithEmailAndPassword(email: email, password: password);
final firebaseUser = userCredential.user;
if (firebaseUser == null) {
throw const AuthenticationException(message: 'Failed to sign in with email and password');
}
return firebaseUser;
} catch (e) {
print(e);
rethrow;
}
}
}
You can read the official documentation here.
Step 5: Google Sign-In
Beyond email and password, integrating social sign-ins like Google provides users with a convenient and quick authentication option. Here’s how to enable and implement Google Sign-In.
Let’s start with enabling Google in the Firebase Console.
You must provide the SHA-1 release fingerprint to Firebase to use Google Sign-In for Android. Follow the instructions in the console if you haven’t provided it yet. After enabling, you might need to download the Google Service (
google-services.json
,GoogleService-Info.plist
) files again.
Add the google_sign_in package to your pubspec.yaml file:
dependencies:
# ...
google_sign_in: latest_version
Implement Google Sign-In.
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
class FirebaseAuthenticationRepository{
final FirebaseAuth firebaseAuth = FirebaseAuth.instance;
final GoogleSignIn googleSignIn = GoogleSignIn();
/* ... */
Future<User> signInWithGoogle() async {
try {
final googleUser = await googleSignIn.signIn();
final googleAuth = await googleUser?.authentication;
if (googleAuth == null) {
throw const AuthenticationException(message: 'Failed to sign in with Google');
}
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
final userCredential = await firebaseAuth.signInWithCredential(credential);
final firebaseUser = userCredential.user;
if (firebaseUser == null) {
throw const AuthenticationException(message: 'Failed to sign in with Google');
}
return firebaseUser;
} catch (e) {
print(e);
rethrow;
}
}
}
There is a different approach on the web. You can check out the documentation to add Google Sign-In for the web.
If you need to generate the SHA-1 fingerprint, you can go to the /android
folder, call the command, check the result, and find the correct SHA-1 fingerprint.
./gradlew signingReport
If you get the error below, you just need to add a couple of code to your Info.plist
file.
Error:
Unhandled Exception: PlatformException(google_sign_in, Your app is missing support for the following URL schemes: com.googleusercontent.apps.52343984866-k6b1g5glo4tfhvevocb1qe1tci35lu20, NSInvalidArgumentException, null)
Solution:
<plist version="1.0">
<dict>
<!-- ... other existing entries ... -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<!-- Replace with your reversed client ID -->
<string>com.googleusercontent.apps.52343984866-k6b1g5glo4tfhvevocb1qe1tci35lu20</string>
</array>
</dict>
</array>
<!-- ... other existing entries ... -->
</dict>
</plist>
You can read the official documentation here.
Step 6: Apple Sign-In
After implementing Google Sign-In to provide a streamlined login experience, we now focus on integrating Apple Sign-In, a critical option for users in the Apple ecosystem.
Enable Apple in the Firebase Console.
There are also some required steps for each platform. You can find and follow the instructions on the console. For iOS, you need to add Sign in With Apple capabilities with the callback URL above.
Implement the Apple Sign-In. You don’t need any other 3rd party package.
class FirebaseAuthenticationRepository{
final FirebaseAuth firebaseAuth = FirebaseAuth.instance;
/* ... */
Future<User> signInWithApple() async {
final appleProvider = AppleAuthProvider();
try {
final userCredential = await firebaseAuth.signInWithProvider(appleProvider);
final firebaseUser = userCredential.user;
if (firebaseUser == null) {
throw const AuthenticationException(message: 'Failed to sign in with Apple');
}
return firebaseUser;
} catch (e) {
print(e);
rethrow;
}
}
}
There is another way for the web. You can check out the documentation to add Apple Sign-In for the web.
You can read the official documentation here.
Step 7: Linking Anonymous Account with Other Providers
Now that we’ve covered various authentication methods like email, Google, and Apple Sign-In, let’s explore how Firebase allows us to link anonymous accounts to these sign-in options, ensuring users retain their data and experience a seamless transition to authenticated accounts.
You can allow users to enter the app without sign-in (anonymous authentication) and sign in the future. But when the user signs in in another way, you will lose all the user’s data. There is a way to link them. Let’s look at it.
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
class FirebaseAuthenticationRepository{
final FirebaseAuth firebaseAuth = FirebaseAuth.instance;
final GoogleSignIn googleSignIn = GoogleSignIn();
/* ... */
Future<User> linkWithEmailAndPassword(String email, String password) async {
try {
final currentUser = firebaseAuth.currentUser;
if (currentUser == null) {
throw const AuthenticationException(message: 'User not found');
}
final credential = firebase_auth.EmailAuthProvider.credential(email: email, password: password);
final userCredential = await currentUser.linkWithCredential(credential);
final firebaseUser = userCredential.user;
if (firebaseUser == null) {
throw const AuthenticationException(message: 'Failed to link with email and password');
}
return firebaseUser;
} catch (e) {
print(e);
rethrow;
}
}
Future<User> linkWithGoogle() async {
try {
final currentUser = firebaseAuth.currentUser;
if (currentUser == null) {
throw const AuthenticationException(message: 'User not found');
}
final googleUser = await googleSignIn.signIn();
if (googleUser == null) {
throw const AuthenticationException(message: 'Google Sign-In was cancelled');
}
final googleAuth = await googleUser.authentication;
final accessToken = googleAuth.accessToken;
final idToken = googleAuth.idToken;
if (accessToken == null || idToken == null) {
throw const AuthenticationException(message: 'Failed to get Google auth tokens');
}
final authCredential = firebase_auth.GoogleAuthProvider.credential(
accessToken: accessToken,
idToken: idToken,
);
final userCredential = await currentUser.linkWithCredential(authCredential);
final firebaseUser = userCredential.user;
if (firebaseUser == null) {
throw const AuthenticationException(message: 'Failed to link with Google');
}
return firebaseUser;
} catch (e) {
print(e);
rethrow;
}
}
Future<User> linkWithApple() async {
try {
final currentUser = firebaseAuth.currentUser;
if (currentUser == null) {
throw const AuthenticationException(message: 'User not found');
}
final appleProvider = firebase_auth.AppleAuthProvider();
final userCredential = await currentUser.linkWithProvider(appleProvider);
final firebaseUser = userCredential.user;
if (firebaseUser == null) {
throw const AuthenticationException(message: 'Failed to link with Apple');
}
return firebaseUser;
} catch (e) {
print(e);
rethrow;
}
}
}
If you check the Users tab on the console after anonymous sign-in, you will see that a UID will be generated for the user.
The identifier will be updated after linking the user with another provider and the UID will be protected.
You can read the official documentation here.
Step 8: Sign out
Once users are authenticated, they might need the option to log out of their accounts. Firebase Authentication makes it straightforward to implement a sign-out functionality in your Flutter app.
To allow users to sign out:
- Access the Firebase Authentication instance.
- Call the
signOut()
method, which will sign out the currently logged-in user.
class FirebaseAuthenticationRepository{
final FirebaseAuth firebaseAuth = FirebaseAuth.instance;
/* ... */
Future<void> signOut() async {
try {
await firebaseAuth.signOut();
} catch (e) {
print(e);
rethrow;
}
}
When a user signs out, Firebase clears their session, and their User
object becomes null
. It's essential to update your app's state accordingly, such as redirecting users to a login page or restricting access to certain features until they log in again.
Now that you’ve implemented the core authentication features, it’s time to ensure everything works correctly under different scenarios. In the next step, we’ll explore how to test and debug your authentication setup without affecting production data. This involves using the Firebase Local Emulator Suite, a powerful tool for simulating Firebase services on your local machine.
Step 9: Testing and Debugging
You can use the Firebase Local Emulator Suite to develop and test your authentication processes on a local system. By running a local version of Firebase Authentication, you can safely simulate sign-ins, sign-outs, and account creation flows — helping you diagnose potential issues before deploying to a live environment.
- Local Testing: You don’t need to make network calls to real backends; the emulator handles requests locally.
- Debugging Tools: You can observe and debug how your app behaves during various stages of the authentication flow.
- Faster Iteration: Testing locally speeds up development cycles and prevents accidental data manipulation in your production project.
Using the emulator suite is highly recommended to confirm that your sign-out and sign-in logic behaves as intended under all circumstances. Once you verify your code locally, you can confidently move toward production deployment.
You can read the official documentation here.
Wrap up
Authentication is a cornerstone of modern app development, ensuring secure and personalized user experiences. Firebase Authentication simplifies this complex process, offering a reliable, scalable, and feature-rich solution for managing user accounts across platforms.
By integrating Firebase Authentication into your app, you’ve unlocked the potential to create seamless login experiences, support diverse authentication methods, and enhance app security. From anonymous sign-ins to robust third-party integrations like Google and Apple, Firebase empowers developers to focus on building exceptional user experiences without worrying about backend complexities.
Remember, testing and debugging are just as critical as implementation. The Firebase Local Emulator Suite provides a safe environment to refine your authentication flows and ensure everything runs smoothly before going live.