Back4App

Sign In with Apple Tutorial

Introduction

Sign In with Apple enables users to sign in to Apps using their Apple ID.
This feature is available on iOS 13 and later, and Parse 3.5 and later.

Prerequisites

To begin with this tutorial, you will need:

Step 1 - Create a New Back4App App

First of all, it’s necessary to make sure that you have an existing app created at Back4App. However, if you are a new user, you can check this tutorial to learn how to create one.

Step 2 - Add the Sign In with Apple capability to your XCode project

In your XCode Project, click on the Target (1) and go to the Signing & Capatbilities tab (2).
Click the + Capability button (3) and add the Sign In with Apple capability (4).
While there, choose your Bundle Identifier (5) and hold that information because we will need it later.

ID

Step 3 - Create a new Service ID

Log into your Apple Developer account and go to the Identifiers section. Check if your created Bundle Identifier is there

ID

Click the Bundle Identifier and scroll down. Check if the Sign In with Apple is selected

ID

Click Edit and make sure the Enable as a primary App ID is selected

ID

If everything is right, save and exit.

Step 4 - Set up Parse Auth for Apple

Go to Back4App website, log in and then find your app. After that, click on Server Settings and search for the Apple Login block and select Settings.

The Apple Login section looks like this:

ID

Now, you just need to paste your Bundle ID in the field below and click on the button to save.

ID

In case you face any trouble while integrating Apple Login, please contact our team via chat!

Step 5 - Option 1 - Download our Template

There is some coding involved in making Sign in With Apple to work, so we created this template that you can download, change the Bundle Identifier, the App Id and Client Key.

The code is fully documented so it is a good starting point.

If you prefer to read through this doc, please go on to the next step.

Step 6 - Option 2 - Manually write code

Inside your view, add the AuthenticationServices framework and create the AuthDelegate that will handle the PFUserAuthenticationDelegate:

1
2
3
4
5
6
7
8
9
10
11
import AuthenticationServices

class AuthDelegate:NSObject, PFUserAuthenticationDelegate {
    func restoreAuthentication(withAuthData authData: [String : String]?) -> Bool {
        return true
    }
    
    func restoreAuthenticationWithAuthData(authData: [String : String]?) -> Bool {
        return true
    }
}

Step 7 - Implement your Delegates for the ViewController

Implement the ASAuthorizationControllerDelegate and ASAuthorizationControllerPresentationContextProviding for the ViewController:

1
class ViewController: UIViewController, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding

Step 8 - Add the Sign In with Apple button

The ViewDidAppear is a good place for it. If you choose other place, remember to call it just once:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        // Sign In with Apple button
        let signInWithAppleButton = ASAuthorizationAppleIDButton()

        // set this so the button will use auto layout constraint
        signInWithAppleButton.translatesAutoresizingMaskIntoConstraints = false

        // add the button to the view controller root view
        self.view.addSubview(signInWithAppleButton)

        // set constraint
        NSLayoutConstraint.activate([
            signInWithAppleButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 50.0),
            signInWithAppleButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -50.0),
            signInWithAppleButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -70.0),
            signInWithAppleButton.heightAnchor.constraint(equalToConstant: 50.0)
        ])

        // the function that will be executed when user tap the button
        signInWithAppleButton.addTarget(self, action: #selector(appleSignInTapped), for: .touchUpInside)
    }

The appleSignInTapped in the last line must also be defined inside the ViewController class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// This is the function that will be executed when user taps the button
    @objc func appleSignInTapped() {
        let provider = ASAuthorizationAppleIDProvider()
        let request = provider.createRequest()
        // request full name and email from the user's Apple ID
        request.requestedScopes = [.fullName, .email]

        // pass the request to the initializer of the controller
        let authController = ASAuthorizationController(authorizationRequests: [request])
        
        // similar to delegate, this will ask the view controller
        // which window to present the ASAuthorizationController
        authController.presentationContextProvider = self
        
        // delegate functions will be called when user data is
        // successfully retrieved or error occured
        authController.delegate = self
          
        // show the Sign-in with Apple dialog
        authController.performRequests()
    }

Step 9 - The presentationContextProvider

The presentationContextProvider (ASAuthorizationControllerPresentationContextProviding) will ask for which window should display the Authorization dialog. As we are going to display it in the same window, we must return self.view.window:

1
2
3
4
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
        // return the current view window
        return self.view.window!
    }

Step 10 - Handling the delegate ASAuthorizationControllerDelegate

There are a few options that we must handle when the delegate is called, so let’s add some code to handle those options distinctly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        print("authorization error")
        guard let error = error as? ASAuthorizationError else {
            return
        }

        switch error.code {
        case .canceled:
            // user press "cancel" during the login prompt
            print("Canceled")
        case .unknown:
            // user didn't login their Apple ID on the device
            print("Unknown")
        case .invalidResponse:
            // invalid response received from the login
            print("Invalid Respone")
        case .notHandled:
            // authorization request not handled, maybe internet failure during login
            print("Not handled")
        case .failed:
            // authorization failed
            print("Failed")
        @unknown default:
            print("Default")
        }
    }

Step 11 - Handling the delegate for didCompleteWithAuthorization

When we successfuly authenticate, we can retrieve the authorized information:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        
        if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
            // unique ID for each user, this uniqueID will always be returned
            let userID = appleIDCredential.user
            print("UserID: " + userID)
            
            // if needed, save it to user defaults by uncommenting the line below
            //UserDefaults.standard.set(appleIDCredential.user, forKey: "userID")
            
            // optional, might be nil
            let email = appleIDCredential.email
            print("Email: " + (email ?? "no email") )
            
            // optional, might be nil
            let givenName = appleIDCredential.fullName?.givenName
            print("Given Name: " + (givenName ?? "no given name") )
            
            // optional, might be nil
            let familyName = appleIDCredential.fullName?.familyName
            print("Family Name: " + (familyName ?? "no family name") )
            
            // optional, might be nil
            let nickName = appleIDCredential.fullName?.nickname
            print("Nick Name: " + (nickName ?? "no nick name") )
            /*
                useful for server side, the app can send identityToken and authorizationCode
                to the server for verification purpose
            */
            var identityToken : String?
            if let token = appleIDCredential.identityToken {
                identityToken = String(bytes: token, encoding: .utf8)
                print("Identity Token: " + (identityToken ?? "no identity token"))
            }

            var authorizationCode : String?
            if let code = appleIDCredential.authorizationCode {
                authorizationCode = String(bytes: code, encoding: .utf8)
                print("Authorization Code: " + (authorizationCode ?? "no auth code") )
            }

            // do what you want with the data here
            
        }
    }

That’s the place where we can also add code for logging in Parse. So right after the do what you want with the data here comment, let’s add:

1
2
3
4
5
6
7
8
9
10
11
PFUser.logInWithAuthType(inBackground: "apple", authData: ["token": String(identityToken!), "id": userID]).continueWith { task -> Any? in
                if ((task.error) != nil){
                    //DispatchQueue.main.async {
                        print("Could not login.\nPlease try again.")
                        print("Error with parse login after SIWA: \(task.error!.localizedDescription)")
                    //}
                    return task
                }
                print("Successfuly signed in with Apple")
                return nil
            }

And of course add the Parse framework:

1
import Parse