How to implement Firebase Auth with Sign in with Apple using SwiftUI

Today in this article we will take a look at how to integrate Sign IN with Apple with Firebase Auth. We will use SwiftUI to design the UI of the app. You can download the source code of this app here.

\ The app won't have much UI. We will just add a simple "Sign in with Apple" button on the screen and when its tapped should ask the user for Face ID/Passcode and then take user to Home view. We will add persistent login as well. So first let's go ahead and set up the Firebase console, and Xcode for the project.

\ \

1. Setting up Firebase Project:

Head over to Firebase.com and create a new account. Once logged in, create a new project in the Firebase Console.

  • Head over to Firebase.com and create a new account or log in.
  • Create a new project in Firebase Console
  • Once a new project is created, click Authentication.
  • Select "Get Started" and then make sure you enable "Apple" under sign-in methods.

Next, we will add our iOS app with the Firebase console. Here is how it goes:

  • Head over to "Project Overview" and select "Add iOS App".
  • This will ask you for your iOS app bundle ID. You can get this ID from the Xcode, select your Target > General > Bundle Identifier
  • Next, download the configuration file generated at the next step to your computer (GoogleService-Info.plist) - we will simple drag and drop this .plist file in the root folder of our Xcode Project

Next we will need to install Firebase SDK. If you don't have Firebase already install then you have one of two ways. You can use CocoaPods or Swift Package Manager. I used Swift Package Manager to install SDK on my Xcode. Here is how to do this:

  • In Xcode go to File > Swift Packages > Add Package Dependency…
  • You will see a prompt appear, please add this link there: <https://github.com/firebase/firebase-ios-sdk.git>
  • Select the version of Firebase you would like to use. Make sure it's the latest one.
  • Choose the Firebase products you would like to install.

\

2. Setting up Xcode:

In Xcode head over to "Signing and Capabilities", click on the "+" icon on the top bar of Xcode and select "Sign in with Apple". That's it.

\ For this project we will have 3 views, the `LoginView,` a `HomeView` and a `ContentView`. Let's create the `LoginView` first. First import `AuthenticationServices` module in `LoginView.swift` file. Next, add the `Sign in with Apple` button:

\ ```javascript import SwiftUI import AuthenticationServices struct LoginView: View { @StateObject var loginData = LoginViewModel() var body: some View { SignInWithAppleButton { request in // Code goes here 1 } onCompletion: { (result) in switch result { case .success(let user): print("Login with Firebase") // code goes here 2

        case .failure(let error):
            print(error.localizedDescription)

        }
    }.frame(height: 55)
    .clipShape(Capsule())
    .padding(.horizontal, 30)
}

} ```

\ Now before we start, we need to comply with Apple's anonymized data requirements as well as need to implement some crypto security checks. First create a new file and call it `LoginViewModel`. In the `LoginViewModel` we will have a published property called `nonce`. A `nonce` is a random string which is generated every time a sign in request is made. To generate this `nonce`, please import `CryptoKit` in `LoginViewModel` file.

\ Then, we will create a helper function and we will call it `randomNonceString`. Here is the implementation of the function:

```javascript func randomNonceString(length: Int = 32) -> String { precondition(length > 0) let charset: [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 &lt; charset.count {
   result.append(charset[Int(random)])
   remainingLength -= 1
 }

} }

return result } ```

\ We will create another helper function called `sha256`, here is the implementation:

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

return hashString } ```

\ Now, back to the `LoginViewModel`, please add the following code:

```javascript import SwiftUI import CryptoKit import AuthenticationServices import Firebase

class LoginViewModel: ObservableObject { @Published var nonce = "" @AppStorage("loginStatus") var isLoggedIn = false func authenticate(credential: ASAuthorizationAppleIDCredential) { guard let token = credential.identityToken else { print("Error: Token") return }

    guard let tokenString = String(data: token, encoding: .utf8) else {
        print(&quot;Error: token to string&quot;)
        return
    }

    let firebaseCredential = OAuthProvider.credential(withProviderID: &quot;apple.com&quot;, idToken: tokenString, rawNonce: nonce)

    Auth.auth().signIn(with: firebaseCredential) { result, error in
        if let error = error {
            print(error.localizedDescription)
            return
        }

        print(&quot;User logged in successfully&quot;)
        withAnimation {
            self.isLoggedIn = true
        }
    }
}

}

//.... helper functions go here ```

\ In this `LoginViewModel`, when we create the `authenticate` subroutine, which takes `ASAuthorizationAppleIDCredential`, we will get the credential token which we will convert to string, and use that token string to then sign into Firebase. We also have an `@AppStorage` property which tracks the user's login status. Now let's call this `authenticate` subroutine of `LoginViewModel` from the `SignInWithAppleButton's` closure.

\ So back to `LoginView.swift` file, please add the following code in the closure:

```javascript struct LoginView: View { @StateObject var loginData = LoginViewModel() var body: some View { SignInWithAppleButton { request in loginData.nonce = randomNonceString() request.requestedScopes = [.email, .fullName] request.nonce = sha256(loginData.nonce) } onCompletion: { (result) in switch result { case .success(let user): print("Login with Firebase") guard let credential = user.credential as? ASAuthorizationAppleIDCredential else { print("Error with user credentials") return } loginData.authenticate(credential: credential) case .failure(let error): print(error.localizedDescription)

        }
    }.frame(height: 55)
    .clipShape(Capsule())
    .padding(.horizontal, 30)
}

} ```

\ Here, we are generating a random `Nonce` string, and then passing the `nonce` as well as the `requestedScopes` to the request. The `requestedScopes` include email and fullName , if the user chooses to provide this data. The user can choose to hide the fullName and email, in which case an email address of such domain `privaterelay.appleid.com` is provided. The random `nonce` string is generated every time a request is made and then returned to us in terms of an `identityToken` through `ASAuthorizationAppleIDCredential` object which we use in the `LoginViewModel` code above. Then this identityToken is converted to a string, compared with the raw nonce which we generated earlier and if all is good give us an `OAuthProvider` credential which is used to sign in to Firebase.

\ And that is pretty much it. Now let's implement some more UI features as well sign out button. Implement the following code in `HomeView.swift`:

```javascript import SwiftUI import Firebase

struct HomeView: View { @AppStorage("loginStatus") var isLoggedIn = true var body: some View {

    VStack {
        Text(&quot;Home View&quot;)
        Button {
            DispatchQueue.global(qos: .background).async {
                try? Auth.auth().signOut()
            }

            withAnimation {
                isLoggedIn = false
            }
        } label: {
            Text(&quot;Log Out&quot;)
        }

    }

}

} ```

Here we have a button which when tapped signs out the user from Firebase Auth, and also changes the `isLoggedIn` status to false. Then, in `ContentView.swift` file this is where we are deciding which view to show: the `HomeView` or `LogInView`. Implement the following code in `ContentView`:

```javascript struct ContentView: View { @AppStorage("loginStatus") var isLoggedIn = false var body: some View { if isLoggedIn { HomeView() }else { LoginView() }

}

} ```

\ So, whenever the `@AppStorage` property changes, the `ContentView` is refreshed and the appropriate view is shown.