SwiftUI custom OTP View, Firebase OTP authentication-Part 02
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.
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.
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
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.
Now enter our bundle ID and click Register app.
Download GoogleService-Info.plist and add it to root folder of our XCode project.
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.
Now go to XCode project open project settings and under TARGETS expand URL Types and click + sign.
Paste copied app ID.
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.
Select App IDs.
Select APP as type and click continue.
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.
And last step is to click Register.
Next step is to create APSN Key that will be used in our Firebase project. Go to Keys and click + sign.
Enter Key name and click Continue.
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.
On APNs Authentication Key click on Upload and select our p8 key from folder and fill KeyID and TeamID and click Upolad.
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
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