You are currently viewing Integrate ‘Sign in with Apple’ with Firebase Authentication

Integrate ‘Sign in with Apple’ with Firebase Authentication

In this article, we will look into how you can integrate ‘Sign in with Apple’ with app that is using Firebase Authentication. Before we get started, there are a few things that you need to get ready in order for us to start the integration.


Prerequisites

Add Firebase to Your iOS Project

You will need to add the Firebase SDK to your iOS project and configure Firebase Console accordingly. Check out this guide by Google for more information on how this can be done.

Add ‘Sign in with Apple’ to Your iOS Project

You also need to setup your app to be able to use the new ‘Sign in with Apple’ functionality introduced in iOS 13. Here’s a great tutorial on how to configure your app as well as the Apple developer portal.

I highly recommend anyone that does not have an app that fulfills the above prerequisites to pause at the moment and go through the Firebase setup guide and ‘Sign in with Apple’ tutorial. After you have completed all the configurations, you can head back to this article and learn how to perform the integration.

With all that in mind, assuming you have done the configurations, your app should be able to show an Apple authorisation form which allows users to sign in with their Apple ID. Furthermore, the Firebase SDK should be properly installed as well.

Apple authorisation form
Apple authorisation form

Now is time to get into the interesting part — Integrating ‘Sign in with Apple’ with Firebase Authentication.


Enable Apple as Sign-in Method

Before we get into the coding part, you must first let Firebase know that you want to use Apple as one of the sign-in providers.

To do so, go to your Firebase Console and navigate to the ‘Sign-in Method’ tab inside the ‘Authentication’ section.

Sign-in method in Firebase Console
Sign-in method in Firebase Console

Look for ‘Apple’ in the list of providers and then click on the ‘Enable’ switch to enable Apple as a sign-in provider. You can leave all other fields empty and then click ‘Save’.

Enable Apple as authorisation provider
Enable Apple as authorisation provider

That should do it, let’s head back to Xcode and start working on the code.


Generate a Secure Nonce

According to the Firebase documentation, the first step we need to take is to generate a cryptographically secure nonce and then use it when making an authorisation request from Apple. Luckily the code to generate the nonce has been provided by Firebase.

First, let’s import the CryptoKit module.

import CryptoKit

After that, copy the following 2 functions to your view controller that contains the ‘Sign in with Apple’ button.

private func randomNonceString(length: Int = 32) -> String {
    precondition(length > 0)
    let charset: Array<Character> =
        Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
    var result = ""
    var remainingLength = length
    
    while remainingLength > 0 {
        let randoms: [UInt8] = (0 ..< 16).map { _ in
            var random: UInt8 = 0
            let errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random)
            if errorCode != errSecSuccess {
                fatalError("Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)")
            }
            return random
        }
        
        randoms.forEach { random in
            if remainingLength == 0 {
                return
            }
            
            if random < charset.count {
                result.append(charset[Int(random)])
                remainingLength -= 1
            }
        }
    }
    
    return result
}

private func sha256(_ input: String) -> String {
    let inputData = Data(input.utf8)
    let hashedData = SHA256.hash(data: inputData)
    let hashString = hashedData.compactMap {
        return String(format: "%02x", $0)
    }.joined()
    
    return hashString
}

Next, update the code where you present the ASAuthorizationController.

let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]

// Generate nonce for validation after authentication successful
self.currentNonce = randomNonceString()
// Set the SHA256 hashed nonce to ASAuthorizationAppleIDRequest
request.nonce = sha256(currentNonce!)

// Present Apple authorization form
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()

After the authorisation is successful, Apple will pass the SHA256 hashed nonce unchanged to the authorizationController(:didCompleteWithAuthorization:) delegate method.

In the next section, we will look into how we can use the nonce to perform authentication with Firebase.


Handle Apple Sign In Successful

In the authorizationController(:didCompleteWithAuthorization:) delegate method, extract the credential object from the given ASAuthorization instance.

func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {

    if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
        // Do something with the credential...
    }
}

After getting the credential object, extract the authorised user ID and save it into UserDefaults. The reason for saving the user ID is because the user ID is required when performing the Apple ID credential state check. (more on that later)

// Save authorised user ID for future reference
UserDefaults.standard.set(appleIDCredential.user, forKey: "appleAuthorizedUserIdKey")

Next up, we will need to create a Firebase credential object so that we can authenticate with Firebase. Following is the code to create a Firebase credential object.

// Retrieve the secure nonce generated during Apple sign in
guard let nonce = currentNonce else {
    fatalError("Invalid state: A login callback was received, but no login request was sent.")
}

// Retrieve Apple identity token
guard let appleIDToken = appleIDCredential.identityToken else {
    print("Failed to fetch identity token")
    return
}

// Convert Apple identity token to string
guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
    print("Failed to decode identity token")
    return
}

// Initialize a Firebase credential using secure nonce and Apple identity token
let firebaseCredential = OAuthProvider.credential(withProviderID: "apple.com",
                                                  idToken: idTokenString,
                                                  rawNonce: nonce)

After creating the Firebase credential object, authenticate with Firebase is just a simple function call.

// Sign in with Firebase
Auth.auth().signIn(with: firebaseCredential) { [weak self] (authResult, error) in
    // Do something after Firebase sign in completed
}

With that, you have successfully integrated ‘Sign in with Apple’ with Firebase Authentication.

To test out all your hard work, hit the ‘Run’ button and perform a sign in action using your sample app. After you successfully sign in, head over to Firebase Console, you should be able to see a user has been created.

New user created at Firebase Console
New user created at Firebase Console

Update Display Name

By default, Firebase Authentication does not store the user’s display name when creating a new user in the Firebase Console. However, most of the time you would like to store the user’s display name during user creation.

Fortunately, this can be easily done by making a user profile change request to the Firebase server.

// Mak a request to set user's display name on Firebase
let changeRequest = authResult?.user.createProfileChangeRequest()
changeRequest?.displayName = appleIDCredential.fullName?.givenName
changeRequest?.commitChanges(completion: { (error) in

    if let error = error {
        print(error.localizedDescription)
    } else {
        print("Updated display name: \(Auth.auth().currentUser!.displayName!)")
    }
})

You can insert the above block of code into the Firebase sign-in completion handler. With this, Firebase will store the user’s display name upon a successful sign in.

Here’s the complete implementation of the authorizationController(:didCompleteWithAuthorization:) delegate method.

func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
    
    if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
        
        // Save authorised user ID for future reference
        UserDefaults.standard.set(appleIDCredential.user, forKey: "appleAuthorizedUserIdKey")
        
        // Retrieve the secure nonce generated during Apple sign in
        guard let nonce = self.currentNonce else {
            fatalError("Invalid state: A login callback was received, but no login request was sent.")
        }

        // Retrieve Apple identity token
        guard let appleIDToken = appleIDCredential.identityToken else {
            print("Failed to fetch identity token")
            return
        }

        // Convert Apple identity token to string
        guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
            print("Failed to decode identity token")
            return
        }

        // Initialize a Firebase credential using secure nonce and Apple identity token
        let firebaseCredential = OAuthProvider.credential(withProviderID: "apple.com",
                                                          idToken: idTokenString,
                                                          rawNonce: nonce)
            
        // Sign in with Firebase
        Auth.auth().signIn(with: firebaseCredential) { (authResult, error) in
            
            if let error = error {
                print(error.localizedDescription)
                return
            }
            
            // Mak a request to set user's display name on Firebase
            let changeRequest = authResult?.user.createProfileChangeRequest()
            changeRequest?.displayName = appleIDCredential.fullName?.givenName
            changeRequest?.commitChanges(completion: { (error) in

                if let error = error {
                    print(error.localizedDescription)
                } else {
                    print("Updated display name: \(Auth.auth().currentUser!.displayName!)")
                }
            })
        }
        
    }
}

Handle User Sign Out

The common practice to handle signing out from Firebase Authentication involves 2 steps:

  1. Sign out from the respective sign-in provider (in our case will be Apple)
  2. Sign out from Firebase

Let’s look into each step in more detail.

Sign Out From Apple

According to Apple documentation, Apple does not provide any API for signing out. This does make sense because users can only sign out from Apple by revoking their Apple ID’s credential in the Settings app.

Therefore, we do not need to do anything to sign out the user from Apple, we only need to remove the saved user ID from UserDefaults, then we are good to go.

// Check provider ID to verify that the user has signed in with Apple
if
    let providerId = currentUser?.providerData.first?.providerID,
    providerId == "apple.com" {
    // Clear saved user ID
    UserDefaults.standard.set(nil, forKey: "appleAuthorizedUserIdKey")
}

Do note that if your app supports sign in using multiple sign-in providers, extra handling might be needed to retrieve the provider ID. However, I will not go into that as that is beyond the scope of this article.

Sign Out From Firebase

To sign out from Firebase, we can simply use the signOut() method provided by the Firebase SDK.

Auth.auth().signOut()

That’s about it, nothing much needs to be done for Firebase sign out.

Here’s the full implementation of the sign out method.

func signOut() throws {
    
    // Check provider ID to verify that the user has signed in with Apple
    if
        let providerId = currentUser?.providerData.first?.providerID,
        providerId == "apple.com" {
        // Clear saved user ID
        UserDefaults.standard.set(nil, forKey: "appleAuthorizedUserIdKey")
    }
    
    // Perform sign out from Firebase
    try Auth.auth().signOut()
}

Handle Apple Credential Revoke

As mentioned in the previous section, users can revoke an Apple ID credential in the Settings app. When this happens, we will have to clear the saved user ID and sign out from Firebase.

There are two situations to handle when the Apple ID credential being revoked:

  1. The credential is being revoked when the app is in background.
  2. The credential is being revoked when the app is terminated.

We will have to handle both situations mentioned above separately.

Handle Credential Revoke When App In Background

According to Apple documentation, a notification will be posted when the app’s Apple ID credential being revoked. We can utilise this notification and perform the sign out action accordingly.

Go ahead and register to the notification in your view controller viewWillAppear(_:) method.

// Register to Apple ID credential revoke notification
NotificationCenter.default.addObserver(self, selector: #selector(appleIDStateDidRevoked(_:)), name: ASAuthorizationAppleIDProvider.credentialRevokedNotification, object: nil)

After that, let’s implement the appleIDStateDidRevoked(_:) method.

@objc func appleIDStateDidRevoked(_ notification: Notification) {
    // Make sure user signed in with Apple
    if
        let providerId = currentUser?.providerData.first?.providerID,
        providerId == "apple.com" {
        signOut()
    }
}

Note that we need to make sure that the user is really signed in with Apple before calling the signOut() method to avoid accidentally signing out a user.

Lastly, make sure to resign from the notification in the viewDidDisappear(_:) method.

notificationCenter.removeObserver(self, name: ASAuthorizationAppleIDProvider.credentialRevokedNotification, object: nil)

Handle Credential Revoke When App is Terminated

When the app is terminated, it is not possible to receive any notifications. Therefore, we can’t rely on the credentialRevokedNotification to notify us on a revoke action.

In this case, we will have to manually check for the Apple ID credential state during app launch. To check for the Apple ID credential state, we can use the getCredentialState(forUserID::) method provided by Apple.

// Retrieve user ID saved in UserDefaults
if let userID = UserDefaults.standard.string(forKey: "appleAuthorizedUserIdKey") {
    
    // Check Apple ID credential state
    ASAuthorizationAppleIDProvider().getCredentialState(forUserID: userID, completion: { [unowned self]
        credentialState, error in
        
        switch(credentialState) {
        case .authorized:
            break
        case .notFound,
             .transferred,
             .revoked:
            // Perform sign out
            try? self.signOut()
            break
        @unknown default:
            break
        }
    })
}

For testing purposes, you can put the above block of code in the viewDidLoad() method of your view controller that contains the ‘Sign in with Apple’ button. However, for most cases, you might want to add it in the AppDelegate‘s application(_:didFinishLaunchingWithOptions:) method.

That’s it, your app is now fully integrated ‘Sign in with Apple’ with Firebase Authentication.


Conclusions

The steps required to integrate ‘Sign in with Apple’ with Firebase is actually quite straightforward. To recap what we have done for the integration:

  1. Enable Apple as a sign-in provider in Firebase Console.
  2. Sign in to Firebase after users successfully signed in with Apple.
  3. Remove saved user ID after users signed out from Firebase.
  4. Sign out from Firebase if users revoked Apple ID’s credential while the app is in background.
  5. Sign out from Firebase during app launch if users revoked Apple ID’s credential while the app is terminated.

Do note that this article is assuming Apple is the only sign-in provider for the app. If your app has more than 1 sign-in provider, you might need to modify the respective sample code accordingly.


Further Readings


I hope this article gives you a good and clear idea of how to integrate ‘Sign in with Apple’ with Firebase Authentication.

If you have any questions, please leave it in the comment section below. If you find this article helpful, feel free to share it with your friends and colleagues.

Follow me on Twitter for more articles related to iOS development.

Thanks for reading! 👨🏼‍💻


👋🏻 Hey!

While you’re still here, why not check out some of my favorite Mac tools on Setapp? They will definitely help improve your day-to-day productivity. Additionally, doing so will also help support my work.

  • Bartender: Superpower your menu bar and take full control over your menu bar items.
  • CleanShot X: The best screen capture app I’ve ever used.
  • PixelSnap: Measure on-screen elements with ease and precision.
  • iStat Menus: Track CPU, GPU, sensors, and more, all in one convenient tool.