Codementor Events

SwiftUI custom OTP View, Firebase OTP authentication-Part 02

Published May 31, 2022

Welcome to part 02 of our short tutorial series. In this part we will implement and connect APNS with Firebase app and send OTP to our device.

First things first lets add stuff to our XCode project.

Lets open our existing xcode project and click on root project and click on our target. Click on + sign and add two capabilites. Push notifications and Background modes.

1_AWuaHn4CCG54FM8bJcMqxg.png
1_MfTiAaNWiftIbLn7hnkN_w.png

After that in Background modes check, Remote notifications. This is needed for us to disable reCAPTCHA Authentication as fallback if push notification does not work.
1_5Tn1pLNiPR4fxUYi0jfN1Q.png

So first, our app is sending phone number for verification(Firebase need to be sure that phone verification comes from our app) and upon completion our app is receiving silent push notification sent from Firebase. When we receive silent push notification, verificationID/token is sent from Firebase in completion handler. Next step for our device to receive SMS which contains verificationCode. Together verificationID and verificationCode will be needed to sign in our user.

Now lets add needed swift packages. Right click on solution and click on Add packages or click on File -> Add packages.

In search field enter this link to GitHub repository:
Repository

1_-22b663dxgbvsDZVttnNsw.png

Next choose FirebaseAuth and FirebaseMessaging as package products.

Next step is to connect Firebase and APNS. Lets first create our Firebase project and register our BundleID. I will not go through process of creating Firebase project so you can check official documentation for detailed explanation.

Enter Firebase project settings. Then click on Add app.
1_n1CYOjLMDrlW4_5benShiQ.png

Now enter our bundle ID and click Register app.

1_ezQXij3vLLApEeNIJntwgA.png

Download GoogleService-Info.plist and add it to root folder of our XCode project.
1_XKiw9N2vBgK_c093F5Yl4w.png
1_dbl2MBLgDU3UJDqvmgAP0Q.png

Now we need to register custom url for our bundle ID in XCode. On Firebase settings scroll down to our iOS app and copy Encoded app ID.

1_OxLEnpx9dZadPoed72vX4Q.png

Now go to XCode project open project settings and under TARGETS expand URL Types and click + sign.
1_QtQ2QYOlCnKaxF62z1h1hg.png

Paste copied app ID.
1_AZ6M3pHFuJJQgjLYViY6rg.png

Next step is to to create Identifier and APNS Key for our app on Apple developer console. Go to https://developer.apple.com/ and log in.

Click on Identifiers and click on + sign to add new identifier.
1_m3_kuI8OiuMN73NlT2qqjg.png

Select App IDs.
1_8mlzenzAIxi-Ee1znWWrug.png

Select APP as type and click continue.
1_0OUmgDPqufvPqCNAyLYcng.png

Next enter Description and Bundle ID as same as for Firebase Project iOS ans scroll down to select Push Notifications as capability app and click continue.
1_icXdMM6S0GSFeNieeLHZHw.png
1_H1Orx9t6PycSRp7RLm9iQw.png

And last step is to click Register.
1_loZxKUcZS2IL1HLDlqIUPA.png

Next step is to create APSN Key that will be used in our Firebase project. Go to Keys and click + sign.
1_Pbao5uCm7comK3_N044m-A.png

Enter Key name and click Continue.
1_SAOuluWBape9tJyutDs0QQ.png

After that click Create and Download key. Key will be in p8 format. Also there are two tings to remember KeyID and TeamID as we wil. need it later. Key Id can be found in Keys menu and TeamID can be found membership menu of your Apple developer console.

Now we need to upload it to Firebase project. Now go back to Firebase project and open Project settings. Next click Cloud Messaging. Scroll to our added CustomOTPView app.
1_A8v9Ig5GTDgXp-kRKex0vw.png

On APNs Authentication Key click on Upload and select our p8 key from folder and fill KeyID and TeamID and click Upolad.
1_f544Jbk1ky1k_ZFOd681MQ.png

That is it. We have connected our APNs service with Firebase Project. Next is to add some code to check if it works 😃

First configure firebase auth in app class CustomOtpViewApp.

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        FirebaseApp.configure()
        return true
    }
}

Next, we will add code that allows our app to receive silent push notification.

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        FirebaseApp.configure()
        return true
    }
      
    func application(_ application: UIApplication, didReceiveRemoteNotification notification: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        print("\(#function)")
        if Auth.auth().canHandleNotification(notification) {
            completionHandler(.noData)
            return
        }
    }
}

Next step is to add our AuthenticationService.swift. This service contains signUp and SingIn functions that will request phone verification and receive verificationID and verificationCode.

import Foundation
import Firebase
import PromiseKit

class AuthenticationService  {
    static func signUp(phoneNumber: String) -> Promise<String> {
        return Promise { seal in
            PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber, uiDelegate: nil) { verificationID, error in
                if error != nil {
                    seal.reject(FirebaseError.Error)
                    return
                }
                guard let verificationID = verificationID else {
                    seal.reject(FirebaseError.VerificatrionEmpty)
                    return
                }
                seal.fulfill(verificationID)
            }
        }
    }
    
    static func signIn(verificationID: String, verificationCode: String) -> Promise<AuthDataResult> {
        return Promise { seal in
            let credential = PhoneAuthProvider.provider().credential(
              withVerificationID: verificationID,
              verificationCode: verificationCode
            )
            Auth.auth().signIn(with: credential) { authResult, error in
                if error != nil {
                    seal.reject(FirebaseError.Error)
                    return
                }
                guard let authResult = authResult else {
                    seal.reject(FirebaseError.VerificatrionEmpty)
                    return
                }
                seal.fulfill(authResult)
            }
        }
    }
    
    static func signOut() {
        if Auth.auth().currentUser != nil {
            do {
                try Auth.auth().signOut()
            }
            catch {
              print (error)
            }
        }
    }
}

Next create AuthViewModel.swift which use our AuthenticationService.

import Foundation
import PromiseKit
import Firebase

class AuthViewModel: ObservableObject {
    func signUp(phoneNumber: String) -> Promise<String> {
        return AuthenticationService.signUp(phoneNumber: phoneNumber)
    }

    func signIn(verificationID: String, verificationCode: String) -> Promise<AuthDataResult> {
        return AuthenticationService.signIn(verificationID: verificationID, verificationCode: verificationCode)
    }

    func signOut(){
        AuthenticationService.signOut()
    }
}

Now lets edit existing PhoneViewModel.swift and add two new functions that will request verification and authenticate our user.

func requestVerificationID(){
    firstly {
        authViewModel.signUp(phoneNumber: "\(self.countryCodeNumber)\("")\(self.phoneNumber.replacingOccurrences(of: " ", with: ""))")
    }.done(on: DispatchQueue.main) { verificationID in
        self.verificationID = verificationID
    }.catch { (error) in
        print(error.localizedDescription)
    }
}
    
func authenticate(){
    let backgroundQueue = DispatchQueue.global(qos: .background)

    firstly {
        authViewModel.signIn(verificationID: self.verificationID, verificationCode: self.verificationCode)
    }.done(on: backgroundQueue) { (AuthDataResult) in

    }.catch { (error) in
        print(error.localizedDescription)
    }
}

Also edit startTimer function to call requestVerificationID function when timer restarts.

func startTimer() {
    timeRemaining = 60
    timerExpired = false
    self.timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

    //Start new round of verification
    self.verificationCode = ""
    requestVerificationID()
}

Next lets add Utils class that will as our user for permission for sending notifications.

import Foundation
import UserNotifications

class Utils {
    static var shared = Utils()
    
    func requestUserNotifications(){
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
            if success {
                CustomUserDefaults.shared.setPushNotifications(enable: success)
            } else if let error = error {
                print(error.localizedDescription)
            }
        }
    }
}

And last thing is to edit ActivateAccountView.swift to call requestVerificationID from PhoneVIewModel.

//MARK: Body
var body: some View {
    VStack {
        LinearProgressBarView(phoneViewModel: phoneViewModel)
        phoneNumberDesc
        codeDigits
        Spacer()
    }
    .background(Color.backgroundColor)
    .navigationBarBackButtonHidden(true)
    .navigationBarTitle(LocalizedStringKey("activate.account"), displayMode: .inline)
    .onAppear { phoneViewModel.requestVerificationID() }
}

Now lets test our app 😃

1_nHtspQ9ik-Rcee7LpPFEpQ.png

1_Ea0xM7QI0kmCaZ-4aYT72w.png

Now you can sign in using verificationID and received verificationCode.

That is it, hope you have enjoyed this tutorial, and stay tuned for part 03 and more content.

As always, the code is available in the GitHub repository:
Repository

Discover and read more posts from Kenan Begić
get started