ELI5: “Sign in with Apple” for React Native using Expo SDK35 & Node.js
Tags: javascript, node, expo, reactnative
Published: 2020-01-23 20:55:14

![alt text](https://miro.medium.com/max/2560/0*FZ0g4OQ502exhoYL.png "Sign in with Apple")

*Thanks to [Vincent de Lafontaine](https://medium.com/@vincevalium), without his help this article wouldn’t exist.*

*We too usually skip to the code snippets, so we won’t judge if you do. Just keep in mind that there’s a bunch of configuration to do on the Apple Developer side of things.*

The Expo documentation regarding “Sign in with Apple” is pretty straightforward : you create a button, it calls a function, the user puts in their Apple info, and *la dee da* you are authentified.

Here’s the button almost exactly as what [Expo shows you](https://docs.expo.io/versions/latest/sdk/apple-authentication/)

```javascript
<AppleAuthentication.AppleAuthenticationButton
  buttonType={
    AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN
  }
  buttonStyle={
    AppleAuthentication.AppleAuthenticationButtonStyle.WHITE
  }
  cornerRadius={5}
  style={{
    width: 150,
    height: 40,
    marginRight: 20,
  }}
  onPress={this._loginWithApple}
/>
```

note that you’ll have to pass it a width and a height, otherwise the button won’t show up (that’s the [official workaround for now](https://github.com/expo/expo/issues/5690))

When you press the button, it should call this :

```javascript

applePayload = await AppleAuthentication.signInAsync({
  requestedScopes: [
    AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
    AppleAuthentication.AppleAuthenticationScope.EMAIL,
  ],
});
```

`signInAsync` returns a promise that resolves to an object containing an `authorizationCode`(among other things). This is what you want to send to your backend.

But what then? What do you have to do on the backend to handle this information?

You know the drill : your route gets an access token from the provider, you send the access token to their API, if valid it returns an object containing some info about the user such as name, email, etc. that you can use in your login or signup flow. You’ve already done it for Google, Facebook, Twitter, Myspace (yeah, no).

It should be easy.

Well it ain’t. Apple said “screw this, it’s not good enough for us” and did things a bit differently. You know Apple, they always need to do things differently.

Here are a few things to know about the apple sign in before we start :

* If you want to validate your authorizationCode with Apple, you need to be enrolled in the Apple Developer Program (💰).

* The user can decide to “Sign in with Apple” and choose not to share their email address, in which case you’ll receive a proxy email instead.

* Apple will only send you back the user’s email (or proxy email) the first time a specific user authentifies themselves. This means that if the user logs out and logs back in, you won’t get their e-mail the second time around. So keep in mind that if you need the user’s email, you should persist it.

* The whole frontend part of this flow will only work on standalone builds. It will fail if you try it within Expo.

* “Sign in with Apple” only works for iOS 13, and the button will only appear if that condition is met.


To give you a bigger picture, these will be the steps to follow (all these points will be explained in detail later) :

**Pre step** : Do all the configuration on the Apple Developer portal

**Step 1** : Create a route on your backend that will receive the payload 
obtained from the Expo AppleAuthentification module (as shown previously in the signInAsync example). This payload contains an `authorizationCode`

**Step 2** : Create your own json web token (jwt) which will be your `clientSecret`

**Step 3** : Authenticate your `authorizationCode`

If successful, Apple will send you back a [tokenResponse](https://developer.apple.com/documentation/signinwithapplerestapi/tokenresponse).

## Pre step : Start with all the Apple Developer config stuff

* Login on the [Apple Developer](https://developer.apple.com/) portal

* Go to **Certificates, Identifiers & Profiles**

* Select **Identifiers**, choose your app and check the “Sign in with Apple” capability. If it’s your first time doing this, you’ll need to create a new app identifier. Make sure to use the same bundle id as in your app.json

* Go to **keys** and create a new key, give it a pertinent name and make sure to check “Sign in with Apple”. Download the key when you’ll have the opportunity and keep it somewhere safe

Devs who implement “Sign in with Apple” on web have a few more steps to follow, but we don’t need to worry about them #mobileDevMasterrace (just kidding.)

You should then have the following items :

* **Team Id** : you can find it if you go to **Membership details**, but it’s also on the app’s Identifier page

![alt text](https://miro.medium.com/max/794/1*eLqH1pzrjY5j2bVnIT5Nfw.png "Team ID screenshot")

* **Key Id** : You can get it in the Keys section you just created

![alt text](https://miro.medium.com/max/270/1*9gUonTEB5gs0kDRN0-pl4w.png "Key Id Details")

* A secret key file that has a “.p8” extension

* Not related to the Apple Developer stuff, but make sure you also have a bundle id (the same as in your app.json file).

## Step 1 : Your backend receives a payload from your mobile app

Not much to do here other than get the attribute `authorizationCode` out of the payload.

```javascript
const { authorizationCode } = req.body.applePayload;
```

## Step 2 : Create a clientSecret jwt

This part draws inspiration directly from [this article](https://medium.com/techulus/how-to-setup-sign-in-with-apple-9e142ce498d4) (thanks [Arjun Komath](https://medium.com/@arjunkomath)!)

You’ll need your **key id**, your **team id** and your **bundle id**, as well as your **secret key file**.

In the following code snippet, I’ll be using `fs-extra` as a drop-in replacement for the module `fs` from node. The advantage of fs-extra over fs is that `readFile` is already promisified and can be awaited.

```javascript
import fs from 'fs-extra';
import jwt from 'jsonwebtoken';

const getClientSecret = async () => {
  const privateKey = await fs.readFile(process.env.PRIVATE_KEY_FILE_PATH);
  const headers = {
    kid: process.env.APPLE_KEY_ID,
    typ: undefined,
    alg: 'ES256',
  };
  const claims = {
    iss: process.env.APPLE_TEAM_ID,
    aud: 'https://appleid.apple.com',
    sub: process.env.APPLE_BUNDLE_ID,
  };
  const token = jwt.sign(claims, privateKey, {
    algorithm: 'ES256',
    header: headers,
    expiresIn: '24h',
  });
  return token;
};
```

## Step 3 : Validate your authorization code

This part is flexible, feel free to fetch in whichever way you prefer. The idea is just to pass it `authorizationCode`, `clientSecret` and `clientId` (=== your bundle id). Also `grant_type=authorization_code` as we’re validating the authorization code.

```javascript
const urlBody = `code=${authorizationCode}&client_secret=${clientSecret}&client_id=${clientId}&grant_type=authorization_code`;

const res = await fetch(`https://appleid.apple.com/auth/token`, {
  method: 'POST',
  body: urlBody,
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
  },
});
```

That’s it. You either get a 200 and a [tokenResponse](https://developer.apple.com/documentation/signinwithapplerestapi/tokenresponse) or a 400.

## “But if Apple doesn’t give me the user’s email every time, how do I keep track of users?”

Both the payload sent from your mobile app and the token sent by Apple contain an identity token (the expo module calls it `identityToken` while Apple calls it `id_token`). These tokens can be decoded using `jwt.decode`. Once decoded, they contain a sub attribute which is unique to each user. So you can simply add a sub field to your user, and validate them that way.

### Further reading

Most helpful post on the subject : [How to setup sign-in with apple — Arjun Komath](https://medium.com/techulus/how-to-setup-sign-in-with-apple-9e142ce498d4)

For more info regarding the Apple Developer set-up part : [What the heck is sign in with Apple — Aaron Parecki](https://developer.okta.com/blog/2019/06/04/what-the-heck-is-sign-in-with-apple)

[Apple documentation on validating tokens](https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens)