Sign In with Apple
Introduction
When integrating third-party sign-in methods on an iOS app, it is mandatory to add the sign in with Apple option as an additional alternative. For this purpose, Apple introduced the AuthenticationServices framework. This framework allows developers to seamlessly integrate a Sign in with Apple button on any Xcode project.
In this repository we provide a simple Xcode template where you can test the different sign in methods we are implementing. This example was already introduced in the log in guide. You can revisit it for more details about the project.
Prerequisites
To complete this quickstart, you need:
- A recent version of Xcode.
- An Apple developer account with a non-personal developer team.
- An application created on Back4App.
- Follow the New Parse App tutorial to learn how to create a Parse application on the Back4App platform.
- Note: Follow the Install ParseSwift SDK Tutorial to create an Xcode Project connected to Back4App.
Goal
To integrate a user sign-in feature using the AuthenticationServices framework and ParseSwift SDK.
Step 1 - Setting up Sign in with Apple
- Once we have the Xcode project linked to the Back4App application, we proceed to add the Sign in with Apple capability. To do this, select your project from the project navigator and go to the targets section. Select the main target and go to the Signing & Capabilities tab, then click on the + Capability button and add the Sign in with Apple capability:
This is the only configuration needed to start integrating a Sign in with Apple method on an iOS app.
Step 2 - Using the AuthenticationServices framework with ParseSwift
The sign in with Apple flow can be completed in three stages. But before, let us add and set up the button to be used to present the Sign in with Apple flow. In the LogInController
class we add this button:
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
46
47
// LogInController.swift file
import AuthenticationServices
...
class LogInController: UIViewController {
...
private let signInWithAppleButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(UIImage(named: "appleIcon"), for: .normal)
button.imageView?.contentMode = .scaleAspectFit
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
// ...
// Layout configuration
// ...
signInWithAppleButton.addTarget(self, action: #selector(handleSignInWithApple), for: .touchUpInside)
}
}
// MARK: - Sign in with Apple section
extension LogInController: ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding {
@objc fileprivate func handleSignInWithApple() {
// TODO: Here we will implement the sign in procedure
}
// ASAuthorizationControllerDelegate
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
// TODO: Handle the sign-in result
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
showMessage(title: "Error", message: error.localizedDescription)
}
// ASAuthorizationControllerPresentationContextProviding
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
guard let window = view.window else { fatalError("No UIWindow found!") }
return window
}
}
Note that the LogInController
conforms to two new protocols: ASAuthorizationControllerDelegate
and ASAuthorizationControllerPresentationContextProviding
. The first protocol allows us to delegate the sign-in result to the LogInController
class. The second protocol is to determine the UIWindow
where the sign-in sheet is displayed.
We now implement the flow in the handleSignInWithApple()
method.
- In the first stage, we prepare the request and the form. The request is constructed by the
ASAuthorizationAppleIDRequest
class. We get an instance of this class from theASAuthorizationAppleIDProvider
provider and the form by theASAuthorizationController
class. Once we have an instance of the request, we have to provide the scopes we are interested in. So far Apple only gives access to the user’s full name and email. Thus, a standard way to create a request is:
1
2
3
4
5
6
@objc fileprivate func handleSignInWithApple() {
let provider = ASAuthorizationAppleIDProvider()
let request: ASAuthorizationAppleIDRequest = provider.createRequest()
request.requestedScopes = [.fullName, .email]
...
}
- With this request, we construct an
ASAuthorizationController
controller. This controller is in charge of displaying a sheet where the user authenticates and gives the corresponding permissions to complete the sign-in process:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@objc fileprivate func handleSignInWithApple() {
// As requested by apple, we set up the necessary objects to implement the sign in with Apple flow.
// See https://help.apple.com/developer-account/#/devde676e696 for more details
let provider = ASAuthorizationAppleIDProvider()
let request: ASAuthorizationAppleIDRequest = provider.createRequest()
request.requestedScopes = [.fullName, .email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self // Here self is a reference to LoginController
authorizationController.presentationContextProvider = self // Here self is a reference to LoginController
// Presents the sign in with Apple sheet and the result will be handled by the ASAuthorizationControllerDelegate delegate
authorizationController.performRequests()
}
- In the last stage we handle the sign-in result. This is returned via the delegate method
authorizationController(controller:didCompleteWithAuthorization:)
. The last argument in this method is anASAuthorization
class containing all the necessary information about the Apple ID and credentials. This stage is where we associate aUser
object and perform the log in to theBack4App
application. ThisUser
object has the following structure (see the Login guide for more details):
1
2
3
4
5
6
7
8
9
10
11
12
import ParseSwift
struct User: ParseUser {
...
var username: String?
var email: String?
var emailVerified: Bool?
var password: String?
var age: Int?
}
Now, we create a User
object from the data contained in the ASAuthorization
result. We accomplish this by instantiating a ParseApple
object (from User.apple
) and call the login(user:identityToken:)
method:
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
// MARK: - Sign in with Apple section
extension LogInController: ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding {
...
// ASAuthorizationControllerDelegate
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
// We cast the (ASAuthorization) authorization object to an ASAuthorizationAppleIDCredential object
guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential else {
return showMessage(title: "Sign in with Apple", message: "Invalid credential")
}
guard let identityToken = credential.identityToken else {
return showMessage(title: "Sign in with Apple", message: "Token not found")
}
// We log in the user with the token generated by Apple
User.apple.login(user: credential.user, identityToken: identityToken) { [weak self] result in
switch result {
case .success(let user):
// After the login succeeded, we send the user to the home screen
// Additionally, you can complete the user information with the data provided by Apple
let homeController = HomeController()
homeController.user = user
self?.navigationController?.pushViewController(homeController, animated: true)
case .failure(let error):
self?.showMessage(title: "Error", message: error.message)
}
}
}
}
Step 3 - Verifying user sign in and session creation
To make sure that the Google sign-in worked, you can look at your Back4App application dashboard and see the new User
containing the Facebook authData
parameters.
You can also verify that a valid session was created in the dashboard, containing a pointer to the corresponding User
object.
Step 4 - Linking an existing User to an Apple ID
In case your iOS App requires relating an Apple ID to an existing user in your Back4App platform, the ParseApple<User>
object implements the method link(user:identityToken:completion:)
where you pass the user
value and the identityToken
from a ASAuthorizationAppleIDCredential
credential
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let credential: ASAuthorizationAppleIDCredential
guard let identityToken = credentials.identityToken else {
return showMessage(title: "Sign in with Apple", message: "Token not found")
}
User.apple.link(user: credential.user, identityToken: identityToken){ result in
switch result {
case .success(let user):
// Linking succeeded, user object now is linked to the corresponding Apple ID
case .failure(let error):
// Linking failed, handle the error
}
}
Step 5 - Run the app
You can go to this repository and download the example project. Before running the project, make sure you set up the provisioning profiles with the ones associated with your developer account.
Conclusion
At the end of this guide, you learned how to sign in or link existing Back4App users on iOS using the sign in with Apple.