# Step-by-Step Guide to Setting Up Firebase Phone Authentication in Flutter for Android and iOS (APNs, Push Notification and Xcode Setup)

Setting up Phone Verification allows various forms of User Credentials.

Let's see how to setup it up. Also make sure that the app is already connected to the firebase. You can read the following article on how to setup Firebase on your app : [Flutter on Fire](https://harishkunchala.com/flutter-on-fire-connect-your-app-to-firebase-now)

## Step 1: Add Phone Provider to Firebase

### 1.1 Go to [Firebase Console](https://console.firebase.google.com/u/0/)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717622170536/837317ce-1f02-4f53-ba28-3e68f941b83e.png align="center")

### 1.2 Navigate to your Project

In our scenario, we'll be selecting kaida:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717622216980/a2562049-92c1-4df0-9e5f-4731b305bcc3.png align="center")

### 1.3 Select Authentication from the sidebar

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717622273279/566d31b1-f1b5-4e0c-aeb7-cd201e66fc8c.png align="center")

### 1.4 Go to Sign-in method tab.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717622322068/614f2e21-6a04-44db-9a25-89efd267e41e.png align="center")

### 1.5 Enable the Phone sign-in and save

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717622371730/24eceea2-929d-42bc-8903-647aeac8d1be.png align="center")

This is my final sign-in methods:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717622397072/1d51c6c1-b813-48e9-ac22-2bad1b634548.png align="center")

## Step 2: Configure Android for Phone Verification setup

### **Setup SHA-1 has in Firebase Console:**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717622600592/c2b961dc-2d9e-4c40-a3e7-71e77d3fcf20.png align="center")

If you want to learn how to generate SHA-1 and SHA-256 for our app. Make sure to check out the following link: [Registering Android App](https://harishkunchala.com/level-up-your-flutter-app-security-how-to-add-firebase-app-check#heading-step-4-registering-android) Checkout Step 5 inside the link.

## Step 3: Configure iOS for Phone Verification Setup

### **Open Xcode:**

First open the app in Xcode by clicking on iOS folder and then selecting "Open in Xcode" (Similar options on both VSCode as well as Android Studio).

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717624729981/66b9a6c2-c8a8-4b13-98f7-e4ee6ebc6037.png align="center")

### **Setup URL Types:**

Go to the info tab and click on URL Types

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717624924399/3c5ce301-7d9b-4832-a630-c169c763d81c.png align="center")

We need couple of url Types:

1. **Reverse Client Id:**
    

You can find it under our Runner in GoogleService-Info File:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717625544539/cef94ec0-c065-4714-bcc4-d72c566d7f79.png align="center")

So let's go ahead and add our Reverse Client Id. Just copy the value and then create an object in URL types:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717625770457/aae90cfd-9be5-421b-927d-bc41b8b166b4.png align="center")

2. **Encoded App ID:**
    

The next step is to copy the Encoded App ID. We can find it on our Firebase Project Settings:

<mark>We are using Encode App ID for reCAPTCHA verification</mark>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717626027983/dbbf3483-e178-4b2a-a5a0-1ece27c2bd4a.png align="center")

Now we just have to past the value in our URL Types:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717626150677/ae19718e-adc3-4da1-9d8e-44d1619bdc52.png align="center")

3. **Bundle ID:**
    

Finally we just have to paste the bundle ID. we can get the Bundle ID from the general tab:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717626383968/f0b636af-4802-4d20-8f06-265538d3c31c.png align="center")

Let's paste it in the URL Types:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717626506195/9f713261-6703-486d-a159-98f523dbbfbd.png align="center")

Perfect, now with the URL Types added. Let's add APNs and other things to avoid Captcha in real devices.

## APN Keys:

### Download APN Key

In order for the OTP to work without Captcha. We need to upload our APN key to our Firebase Project.

So first let's go to our [Apple Developer Member Center](https://developer.apple.com/account) and the go ahead and click on Keys:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718162796512/f12a6a21-f039-4168-a427-c5a8f387bd0c.png align="center")

**Keys Page:**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718162843581/f1ed72dc-5f5e-4235-b3b0-5ccd0b6434fa.png align="center")

Click on Create a Key.

And then enable Apple Push Notification Service

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718162932391/6155e667-52db-4f28-9f57-b839391dd9e4.png align="center")

After you click continue. You are taken to the new key registration page and just click on register.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718163053858/dd37efeb-098f-4f01-9b9a-2191c740caa9.png align="center")

<mark>Make sure to download your key and store it in a secure place. As it cannot be redownloaded again.</mark>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718163272276/da801321-1dd4-43b8-8fb4-7bd7877c0989.png align="center")

### Upload APN Key to Firebase console:

Now that the key has been downloaded. Let's upload it to Firebase console.

### Push Notifications:

1. Inside your project in the Firebase console, select the gear icon, select **Project Settings**, and then select the **Cloud Messaging** tab.
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718163562059/7890eb95-6e35-4422-ba71-96366262d667.png align="center")

Make sure to enter the Key ID as well as the Team ID when you upload the APN key:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718163917789/c58ed176-dbd4-4ce3-9577-5f69f0c25f80.png align="center")

Perfect now we can finally see our APN key in our Firebase console

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718164103256/d40f39df-5ad3-493a-82cd-bd8fce07edb6.png align="center")

## Push Notifications:

Before our app can start to receive messages. We must explicitly enable "Push Notifications" and "Background Modes" within Xcode

### **Open our iOS module in Xcode:**

You can do it in Android Studio by clicking on iOS module and then from the menu (Tools --&gt; Flutter --&gt; open iOS / macOS module in Xcode)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718164606854/d4646200-5bb7-4e4f-a4f2-149a002a6671.png align="center")

So we get Xcode:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718164741021/f3e47382-df87-4dea-a706-89488d6f0366.png align="center")

### Xcode Settings:

So follow the steps below:

1. Select Project
    
2. Select the Project target.
    
3. Select the "Signing & Capabilities" tab.
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718164986565/365ee645-b7e8-45ab-824c-d670e4f1fde8.png align="center")

### Enable Push Notifications

Next the "Push Notifications" capability needs to be added to the project. This can be done via the "Capability" option on the "Signing & Capabilities" tab:

1. Click on the "+ Capabilities" button.
    
2. Search for "Push Notifications"
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718165248557/a88a6613-8e28-4a25-8c6f-d55b270a7c30.png align="center")

Once selected, the capability will be shown below the other enabled capabilities. If no option appears when searching, the capability may already be enabled.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718165299839/c7abaa38-6376-49b5-af16-2a9257cf595a.png align="center")

### Enable Background Modes

Next the "Background Modes" capability needs to be enabled, along with "Background fetch" and "Remote notifications" sub-modes. This can be again added via the "Capability" option on the "Signing & Capabilities: tab:

1. Click on the "+ Capabilities" button.
    
2. Search for "Background Modes".
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718165883400/ecbf5430-8c92-4a2f-bcbd-d3d27a8f3030.png align="center")

Once selected we can see the "Background Modes" with other capabilities too.

<mark>Now ensure that both the "Background fetch" and the "Remote notifications" sub-modes are enabled:</mark>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1718166009098/6090d8a3-a0be-42b1-bdaf-daffaf798e0c.png align="center")

If you want, you can enable Background processing too.

## Implementing Phone Number Verification

### Add Phone Authentication Dependency

While we have already added it to our [Kaida](https://github.com/khkred/kaida) before. Let's make sure to get the latest version into our app.

```bash
flutter pub add firebase_auth
```

### Implement Phone Verification Method in AuthRepository

**AuthRepository:**

I have created an [AuthRepository](https://github.com/khkred/kaida/blob/main/lib/src/features/auth/repository/auth_repository.dart) in Kaida. So we are going to add this to our AuthRepository

```dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:kaida/src/features/auth/domain/models/app_user.dart';

class AuthRepository {
  final FirebaseAuth _firebaseAuth;
  final FirebaseFirestore _firestore;

  AuthRepository(this._firebaseAuth, this._firestore);

  // Existing methods...

  Future<void> verifyPhoneNumber(
      String phoneNumber,
      PhoneVerificationCompleted verificationCompleted,
      PhoneVerificationFailed verificationFailed,
      PhoneCodeSent codeSent,
      PhoneCodeAutoRetrievalTimeout codeAutoRetrievalTimeout) async {
    await _firebaseAuth.verifyPhoneNumber(
      phoneNumber: phoneNumber,
      verificationCompleted: verificationCompleted,
      verificationFailed: verificationFailed,
      codeSent: codeSent,
      codeAutoRetrievalTimeout: codeAutoRetrievalTimeout,
    );
  }

  Future<UserCredential> signInWithPhoneNumber(String verificationId, String smsCode) async {
    final credential = PhoneAuthProvider.credential(verificationId: verificationId, smsCode: smsCode);
    return await _firebaseAuth.signInWithCredential(credential);
  }
}
```

**AuthNotifier:**

Now let's add the verify `phoneNumber()` in [AuthNotifier](https://github.com/khkred/kaida/blob/main/lib/src/features/auth/provider/auth_notifier.dart)

```dart
class AuthNotifier extends StateNotifier<AuthState> {
  final AuthRepository _authRepository;

  AuthNotifier(this._authRepository) : super(const AuthStateInitial());

  // Existing methods...

  Future<void> verifyPhoneNumber(
    String phoneNumber,
    PhoneVerificationCompleted verificationCompleted,
    PhoneVerificationFailed verificationFailed,
    PhoneCodeSent codeSent,
    PhoneCodeAutoRetrievalTimeout codeAutoRetrievalTimeout,
  ) async {
    try {
      await _authRepository.verifyPhoneNumber(
        phoneNumber,
        verificationCompleted,
        verificationFailed,
        codeSent,
        codeAutoRetrievalTimeout,
      );
    } catch (e) {
      print('VerifyPhoneNumber Error: $e');
      throw e;
    }
  }

  Future<void> signInWithPhoneNumber(String verificationId, String smsCode) async {
    try {
      await _authRepository.signInWithPhoneNumber(verificationId, smsCode);
    } catch (e) {
      print('SignInWithPhoneNumber Error: $e');
      throw e;
    }
  }
}
```

**PhoneVerificationPage:**

We are creating a basic Phone Verification Page that'll take a code and verify it. Make sure to create separate forms for both phone text form field and verification code text form field

```dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:kaida/src/features/auth/provider/auth_providers.dart';

import '../../../../constants/routes.dart';

class PhoneVerificationPage extends HookConsumerWidget {
  const PhoneVerificationPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final phoneFormKey = useMemoized(() => GlobalKey<FormState>());
    final codeFormKey = useMemoized(() => GlobalKey<FormState>());
    final phoneController = useTextEditingController();
    final codeController = useTextEditingController();
    final verificationId = useState<String?>(null);
    final errorMessage = useState<String?>(null);

    final authNotifier = ref.read(authNotifierProvider.notifier);
    
    String formatPhoneNumber(String phoneNumber) {
      
      if(phoneNumber.startsWith('+1')){
        return phoneNumber;
      } else {
        return '+1$phoneNumber';
      }
    }

    return Scaffold(
      appBar: AppBar(
        title: const Text('Verify Phone Number'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            Form(
              key: phoneFormKey,
              child: Column(
                children: [
                  TextFormField(
                    controller: phoneController,
                    decoration:
                        const InputDecoration(labelText: 'Phone Number'),
                    keyboardType: TextInputType.phone,
                    validator: (value) {
                      if (value == null || value.isEmpty) {
                        return 'Please enter your phone number';
                      }
                      if (!RegExp(r'^\+?\d{10,}$').hasMatch(value)) {
                        return 'Please enter a valid phone number';
                      }
                      return null;
                    },
                  ),
                  const SizedBox(height: 20),
                  ElevatedButton(
                      onPressed: () async {
                        if (phoneFormKey.currentState?.validate() == true) {
                          try {
                            await authNotifier
                                .verifyPhoneNumber(formatPhoneNumber(phoneController.text.trim()),
                                    (PhoneAuthCredential credential) async {
                              await FirebaseAuth.instance
                                  .signInWithCredential(credential);
                              ScaffoldMessenger.of(context).showSnackBar(
                                  const SnackBar(
                                      content: Text('Phone number verified')));
                              context.go(Routes.profile);
                            }, (FirebaseAuthException e) {
                              errorMessage.value = e.toString();
                            }, (String mVerificationId, int? resendToken) {
                              verificationId.value = mVerificationId;

                              ScaffoldMessenger.of(context).showSnackBar(
                                  const SnackBar(
                                      content: Text('Verification code sent')));
                            }, (String mVerificationId) {
                              verificationId.value = mVerificationId;
                            });
                          } catch (e) {
                            errorMessage.value = e.toString();
                          }
                        }
                      },
                      child: const Text('Send Verification Code')),
                ],
              ),
            ),
            const SizedBox(height: 20),
            if (verificationId.value != null)
              Form(
                key: codeFormKey,
                child: Column(
                  children: [
                    TextFormField(
                      controller: codeController,
                      decoration:
                          const InputDecoration(labelText: 'Verification Code'),
                      keyboardType: TextInputType.number,
                      validator: (value) {
                        if (value == null || value.isEmpty) {
                          return 'Please enter the verification code';
                        }
                        return null;
                      },
                    ),
                    const SizedBox(height: 20),
                    ElevatedButton(
                        onPressed: () async {
                          if (codeFormKey.currentState?.validate() == true) {
                            try {
                              await authNotifier.signInWithPhoneNumber(
                                  verificationId.value!, codeController.text);
                              ScaffoldMessenger.of(context).showSnackBar(
                                  const SnackBar(
                                      content: Text('Phone Number Verified')));
                             context.go(Routes.profile);
                            } catch (e) {
                              errorMessage.value = e.toString();
                            }
                          }
                        },
                        child: const Text('Verify Code')),
                  ],
                ),
              ),
            if (errorMessage.value != null)
              Padding(
                padding: const EdgeInsets.only(top: 20),
                child: Text(
                  errorMessage.value!,
                  style: const TextStyle(color: Colors.red),
                ),
              ),
          ],
        ),
      ),
    );
  }
}
```

**UserProfilePage:**

For now I am just adding a button to go the Phone Verification Page from our existing [user\_profile\_page.dart](https://github.com/khkred/kaida/blob/main/lib/src/features/profile/presentation/pages/user_profile_page.dart)

```dart
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:kaida/src/features/auth/provider/auth_providers.dart';
import 'package:kaida/src/constants/routes.dart';
import 'package:go_router/go_router.dart';

class UserProfilePage extends HookConsumerWidget {
  const UserProfilePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final authState = ref.watch(authNotifierProvider);

    if (authState is! AuthStateAuthenticated) {
      // Redirect to sign-in if not authenticated
      WidgetsBinding.instance.addPostFrameCallback((_) {
        context.go(Routes.signIn);
      });
      return const SizedBox.shrink();
    }

    return Scaffold(
      appBar: AppBar(
        title: const Text('User Profile'),
        actions: [
          IconButton(
            icon: const Icon(Icons.edit),
            onPressed: () {
              context.go(Routes.editProfile);
            },
          ),
          IconButton(
            icon: const Icon(Icons.lock),
            onPressed: () {
              context.go(Routes.changePassword);
            },
          ),
          IconButton(
            icon: const Icon(Icons.phone),
            onPressed: () {
              context.go(Routes.phoneVerification);
            },
          ),
          IconButton(
            icon: const Icon(Icons.logout),
            onPressed: () {
              ref.read(authNotifierProvider.notifier).signOut();
            },
          ),
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('User ID: ${authState.userId}'),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                context.go(Routes.editProfile);
              },
              child: const Text('Edit Profile'),
            ),
            ElevatedButton(
              onPressed: () {
                context.go(Routes.changePassword);
              },
              child: const Text('Change Password'),
            ),
            ElevatedButton(
              onPressed: () {
                context.go(Routes.phoneVerification);
              },
              child: const Text('Verify Phone Number'),
            ),
            ElevatedButton(
              onPressed: () {
                ref.read(authNotifierProvider.notifier).signOut();
              },
              child: const Text('Logout'),
            ),
          ],
        ),
      ),
    );
  }
}
```

## Testing:

Finally let's test the app. We are going to test it on an iPhone in order for the app to send a code to the user and also to ensure that our verification works on actual device.

![Phone Verification](https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExejdsMXY4c2J3YXdhYmo5am92OGI0aHdpazM4bjhiNmU0aGowZzZndSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/coeagaoyKpznCpZgRW/giphy.gif align="center")

Perfect. Finally the app is working as intended. You can find the code here: [https://github.com/khkred/kaida](https://github.com/khkred/kaida)
