Adding Sign in with Apple to your iOS App

Since April 2020, all apps that use a third-party or social login service are required to offer Sign in with Apple if they want to be accepted in the App Store.

In a previous tutorial, we walked through the process of building a simple clone of Apple's iMessage. In this tutorial, we'll go through the steps of adding Sign in with Apple to that iMessage clone. If you have an existing app built with Stream's iOS Chat SDK and want to support this sign-in method, the steps will be similar, and the differences pointed out. If you're not building a messaging app, a good chunk of the information here will still apply, so stick around!

If you get lost during this tutorial, you can always check the completed project in this GitHub repo. If you run into any errors, there's a Troubleshooting section at the end of the repo's README that you can check on.

What is Sign in with Apple?

Sign in with Apple makes it easy for users to sign in to your apps and websites using their Apple ID. Instead of filling out forms, verifying email addresses, and choosing new passwords, they can use Sign in with Apple to set up an account and start using your app right away. All accounts are protected with two-factor authentication for superior security, and Apple will not track users' activity in your app or website.

Apple Developer Portal: Sign in with Apple

What you need

Step 1: Configuring the client project

If your app is still early in development or using the iMessage clone project, follow the steps in the image below. If your application is already in production, you can jump to the next image.

1.1: Select a device you have signed in with Apple ID and 2FA. 1.2: Select your team. 1.3: Choose a unique bundle id.

Now, you need to add the Sign in with Apple capability in the "Signing & Capabilities" tab of your target.

Screenshot of target settings highlighting the capability button and the Sign in with Apple capability

Step 2: Adding the sign-in button

To add the sign-in button, we'll use the AuthenticationServices framework, which provides all the functionality needed, including UI elements.

We also need to add the authentication screen, which will appear before the contacts screen. Let's create an AuthenticationViewController.swift file and paste in the following code:

https://gist.github.com/cardoso/91bd96dcb0d88a2834510d996b4a61b3

Now, let's add this new screen as the first screen in the storyboard. If you're using the iMessage clone project, it should be the root view controller of the navigation controller. To finish it up, set up a Segue from it to the contacts screen with id kAuthToContactsSegueId:

Storyboard with the auth screen with a segue to the contacts screen

Now, run the project, and you should have a button that, when pressed, leads to the contacts screen.

Animation of the Sign in with Apple button being pressed and the contacts screen showing up

Of course, there is no real authentication happening here yet. We'll look into that in the next steps.

Step 3: Setting up the backend

Before we build real authentication into the client, we'll need a backend that can generate a Stream token when given the Apple ID credentials. If you already have a backend, you can use one of Stream's server-side chat libraries to set it up similarly.

To keep things short, we'll build a simple Node.js/Express backend with one endpoint: /authenticate. We'll also use the node-persist package to persist new user data the first time they authenticate. If you're not interested in building the backend yourself or are having problems, you can get the complete code in the repository.

In the terminal, run the following commands:

https://gist.github.com/cardoso/c17ef0c932fc770f6da9527a1dad5ea0

The following code snippets can be copied in sequence into the index.js.

Let's start coding our index.js by importing the objects we need:

https://gist.github.com/cardoso/0a5dedfcae012c94390bb542572858bc

Now, configure the Stream Chat client with the credentials that you get in your Stream dashboard:

https://gist.github.com/cardoso/9d766a9689430f55a110575dc475d437

Configure the Apple Auth client:

https://gist.github.com/cardoso/580f126a09d8979b8bbcefdfe249616b

For details on filling those parameters, read apple-auth's SETUP.md.

If you're using something other than Node.js for your backend, there are versions of this library for other languages, such as Go. If you can't find it for your preferred backend stack, you'll have to read through apple-auth's source code and Sign in with Apple's API specifications to implement something similar from scratch.

Initialize express and the node-persist storage:

https://gist.github.com/cardoso/d6fc69c07cadc88e870414818e851dbe

Now, let's start building the handler for the /authenticate endpoint by extracting the parameters we need from the request:

https://gist.github.com/cardoso/5457f96a4d617214d7abc2a255d10d34

Now, we verify the appleAuthCode with Apple's servers using apple-auth and extract the user's email address from the response:

https://gist.github.com/cardoso/bc5acbbce6f2e4cfce02fa52457d161b

If we have an email, and a name was supplied in the authentication request we need to store that information in our persistent storage:

https://gist.github.com/cardoso/95d575afba5ad51401d3a6805f2bdb3b

Information other than Apple Uid and auth code is only guaranteed to be given by Apple the first time the user tries to sign in. We use this fact to determine when to register the user, and it's why we need to save the information and resupply it in every authentication response.

Now, let's finish the handler by fetching the user data from local storage and relaying it in the response:

https://gist.github.com/cardoso/a578332666d43a3ee6f18274a300af78

Finally, let's configure the express app to listen on port 4000:

https://gist.github.com/cardoso/1180da7eed9d970ab7f08bf317c60954

Now, we can close index.js and leave the backend running with the following command:

https://gist.github.com/cardoso/c6e0eab1b97ff0322e2922a3a92338c4

If this is working, opening localhost:4000/authenticate in your browser will display Cannot GET /authenticate, which is OK, because we'll use POST in the client application.

Step 4: Request Authorization with Apple ID

Let's go back to Xcode.

First, we need to code the function to interface with the /authenticate endpoint we just created. Let's create a file named Authentication.swift and start by defining the request and response structures.

https://gist.github.com/cardoso/19c15f67f091836bc9a63be2fa4a1286

Thanks to Codable, we can easily transform those objects into and from JSON, which comes in very useful as we define the actual function to interface with /authenticate by sending AuthRequest and receiving AuthResponse:

https://gist.github.com/cardoso/abaa964cbf755e6c1320dff9f7206f19

Reminder: Sign in with Apple will only work on a real device. Make sure to replace [your local ip] with your Mac's local network IP address, which is not localhost nor 127.0.0.1. To find it out, run the following command on a terminal in your Mac:

https://gist.github.com/cardoso/4e6bfb0a0c6b7d037096dcba01ceceb7

Now, to authenticate the user with Apple, let's go back to AuthViewController.swift and edit the button's press handler to add this behavior:

https://gist.github.com/cardoso/7dfb72764c790e466841b785b7f1d57f

This implementation will make pressing the Sign in with Apple button trigger the Sign in with Apple screen.

Animation showing the Sign in with Apple button being pressed and the Sign in with Apple modal showing up

However, it won't compile yet, because we haven't conformed AuthViewController to a couple of required protocols.

We need conform AuthViewController to ASAuthorizationControllerPresentationContextProviding to tell Sign in with Apple which window to render the flow in:

https://gist.github.com/cardoso/d7a18fb5006f41563cd1e00d6125be9c

Most importantly, we also need the conformance to ASAuthorizationControllerDelegate, which will let us receive the Apple credentials after the user completes the sign-in flow, which we can then use to authenticate, then configure our Stream client and move to the contacts screen:

https://gist.github.com/cardoso/0403c2e01cec6e9c2a4a2b9da70a0020

Finally, we have a functioning Sign in with Apple implementation:

Animation showing user authenticating successfully with Sign in with Apple and being moved to the contacts screen

Since it's a new user, we don't have any contacts showing.

The backend logs should look similar to this:

https://gist.github.com/cardoso/64c6681b63db6efcd1e4a8f620d4b57d

Wrapping up

Congratulations! You can now build Sign in with Apple authentication into any iOS app. Make sure to read Sign in with Apple's guidelines and documentation to keep up-to-date with the requirements and announcements. Also, for your apps that need social media or messaging features, check out Stream's documentation for activity feeds and chat messaging.

Thanks for reading and happy coding!

TutorialsChat