How to implement Sign in with Apple

Toni Vujević
Team Lead
Development

Introduction

One of the new things Apple released at WWDC19 with iOS13 update was the Sign in with Apple feature which enables users to sign in to apps and websites easily by using their Apple ID.

It helps users to avoid filling out forms and remembering passwords. Instead, users just need to tap “Sign in with Apple,” and use either Face ID or Touch ID.

Furthermore, Apple has made this feature mandatory for the next version release of all apps which use a third-party or social login service – with immediate implications for a large number of developers. 

There are a few exceptions when Apple does not force you to use their signup method when you already have other third-party signups.

This article explains how to enable Sign In With Apple in your app, and also includes examples of code which explain how to use the feature on the client-side and server-side.

Troubles with conventional signup methods

We are all familiar with conventional signup methods like email with password authentication.

These usually add a somewhat complicated flow to your app, because you need users to remember their passwords, while also having the option to change their password in the app and to retrieve the account by restoring a forgotten password.

Another familiar signup method is using a magic link in a signup email, this is passwordless. Users just press the link you have sent them in their email and iOS takes over and opens the app with a link containing JWT to verify the user.

This can be done by using URL Scheme or by using Universal links.

However, there can still be issues with a small percentage of devices where the iOS does not open the app and only opens the webpage. Even trying to force the app to open by pressing the link for a long time and trying to select the app does not work, meaning that there is obviously a problem with iOS

Benefits of Sign in with apple

Having a third party signup seems like a good idea to finally sort out this cumbersome flow of signing up users into an app.

It has got even more attractive now that Apple finally released their own version of third-party signup; because most users have their devices connected to iCloud, they just need to sign up using the Apple ID that they already have.

Sign in with Apple requires your Apple ID to be protected with two-factor authentication.

Apple also guarantees to never track or profile you when you sign in with Apple, and you also have the option to hide your exact email from the app you sign into.

Sign in with Apple works on all your Apple devices as well as the web and apps on Android or Windows.

Caveats

You must register a Bundle ID when setting up Sign in with Apple (i.e. you can’t use it for web app login and have no associated iOS application).

Technically, you can create a dummy app to obtain a bundle ID – but that’s a bit dodgy though.

Since it’s linked to a specific Apple Developer account, losing your Developer account will disable this functionality – especially a problem if the user decided to hide email address and use proxy (see recent issues with Epic Games)

GUI options and guidelines

Apple guidelines for displaying Sign in with Apple: 

  • The button should be no smaller than other sign-in buttons.
  • You should not make users scroll to see the button.

By not following these guidelines, you risk being rejected in the review process on the new version release. 

Developers have the option to use a predefined system-provided button, or create a custom button.

By using the system-provided APIs to create a Sign in with Apple button, you get out-of-the-box advantages like: 

  • guaranteed use of an Apple-approved title, font, and color 
  • maintenance of ideal proportions as you change its style
  • translation of the title into the language specified by the device
  • configuration of the corner radius value
  • system-provided alternative text label that lets VoiceOver describe the button

You can also select one of these 3 button titles:

  • Sign in with Apple
  • Sign up with Apple
  • Continue with Apple

And 3 options of button style:

  • white 
  • white with outline
  • black

If you are not satisfied with the options available on the system-provided button, you can create a custom Sign in with Apple button for iOS, macOS, or the web. In this case, you still need to follow rules and guidelines defined by Apple.

Enablement

In order for your app to use Sign In with Apple ID, you need to enable the Sign In with Apple ID Capability.

Apple’s developer site

  • On Apple’s developer site you need to enable Sign In with Apple for your app on the Certificates, Identifiers & Profiles page. You will need to update all your provisioning profiles and certificates.

  • Also, under Certificates, Identifiers and Profiles go to the Keys menu and add a new key – give it a name and select “Sign In With Apple”.

  • When using Apple’s developer site to enable this capability, you must also manually add Sign in with Apple Entitlement to your app with the correct associated value.

  • Additionally, if you need to communicate with users using emails, you will need to configure the Apple private email relay service and register your domain and email on the email sources page.

Without this, all the users who choose to use Apple private relay email will not get your mails. You can check for more instructions here.

Xcode configuration

You can also enable Sign In with Apple ID Capability locally from Xcode: 

  1. Open your Xcode project workspace. 
  2. In the project navigator, you need to select ‘Project’ and then select ‘Target’. 
  3. In the project editor on the right side, select Signing & Capabilities
  4. Add Capability by clicking the ‘+’ button above and you can find Sign In with Apple Capability by searching the library.

When you configure capabilities for a target this way, Xcode manages related entitlements and adds them to a property list file with the .entitlements extension, so you don’t need to add this manually, as in the Apple developer site steps.

Implementation

In your code, you need to import the AuthenticationServices framework. AuthenticationServices gives you access to ASAuthorizationAppleIDButton, and this lets you choose one of the built-in button styles and types that were discussed in the GUI options and guidelines section.

After tapping the button, create a request using the ASAuthorizationAppleIDProvider, and then use an instance of ASAuthorizationController to execute the request and listen for results using delegate functions. After successful authorization, you will get the result as ASAuthorizationAppleIDCredential object.

When using cross-platform technology, like React Native framework, you can still use native code to implement this, but I would recommend using a third-party module which will act as a wrapper to AuthenticationServices. I recommend using@invertase/react-native-apple-authentication which exposes AuthenticationServices to React Native javascript code, which also adds support for Android platform.

On your react component, you just need to return AppleButton in JSX of your components render function:

<AppleButton
             buttonStyle={AppleButton.Style.BLACK}
             buttonType={AppleButton.Type.SIGN_IN}
             cornerRadius={theme.radius.button}
             style={styles.appleButton}
             onPress={onAppleAuthPress}
           />

You can move authentication logic to separate service that you can call from your middleware instead of having it directly in React component:

import appleAuth, {
 AppleAuthRequestOperation,
 AppleAuthRequestScope,
 AppleAuthError,
} from '@invertase/react-native-apple-authentication';
 
export const auth = async () => {
 try {
   const appleAuthRequestResponse = await appleAuth.performRequest({
     requestedOperation: AppleAuthRequestOperation.LOGIN,
     requestedScopes: [AppleAuthRequestScope.EMAIL],
   });
   return appleAuthRequestResponse.identityToken;
 } catch (e) {
    …}}};

Server side verifying

You can use the identityToken that you get from a client app inside ASAuthorizationAppleIDCredential and verify the user on the server side. To verify the identity token, you can follow the instructions on Apple’s Verifying a User documentation page.

const NodeRSA = require('node-rsa');
const jwt = require('jsonwebtoken');
const request = require('request-promise');
const APPLE_IDENTITY_URL = 'https://appleid.apple.com';
 
const getAppleIdentityPublicKey = async (kid) => {
 const url = APPLE_IDENTITY_URL + '/auth/keys';
 const data = await request({ url, method: 'GET' });
 const keys = JSON.parse(data).keys;
 const key = keys.find(k => k.kid === kid);
 const pubKey = new NodeRSA();
 pubKey.importKey({ n: Buffer.from(key.n, 'base64'), e: Buffer.from(key.e, 'base64') }, 'components-public');
 return pubKey.exportKey(['public']);
};

I created the getAppleIdentityPublicKey function as a helper function to get a public key using the keyId we can decode from the identityToken server received from a mobile app. The result of the validateIdentityToken function will be jwtClaims which contain the users’ email address.  

const iosBundleId = <Your apps bundle id here>;
 
const validateIdentityToken = async (identityToken, isDev) => {
 try {
   const clientID = iosBundleId;
   const { header } = jwt.decode(identityToken, { complete: true });
   const applePublicKey = await getAppleIdentityPublicKey(header.kid);
   const jwtClaims = jwt.verify(identityToken, applePublicKey, { algorithms: 'RS256' });
   if (jwtClaims.iss !== APPLE_IDENTITY_URL) throw new Error('Apple identity token wrong issuer: ' + jwtClaims.iss);
   if (jwtClaims.aud !== clientID) throw new Error('Apple identity token wrong audience: ' + jwtClaims.aud);
   if (jwtClaims.exp < moment.utc().unix()) throw new Error('Apple identity token expired');
   return jwtClaims;
 } catch (err) {
   ...
   return null;
 }
}

Based on the result of this function, you verify the users email, and complete their signup to your app. 

You can also avoid doing the last step of verifying with refresh token once a day from Apple’s diagram. 

You can do this by sending your own access token, which is generated on your server, to the client app after you verify authorization code – just like you would do when authenticating users’ emails and passwords.

Conclusion

We can assume that we will see this feature in a lot of apps now because Apple has made it mandatory for every app that uses third-party login services.

The good thing is that it is very easy to implement by using the AuthenticationServices framework on iOS, or the modules that wrap around it to expose it to some cross-platform technologies.

It is straightforward if you need just the basic UI element which is predefined by the system, but becomes a little more complicated when you need to use your custom UI buttons, but at least the option is there for everyone that needs it.

I hope this article was informative – if you have any questions, or want to work with an experienced iOS team, feel free to contact us at business@decode.agency