HOW-TO: Sign in with Apple on React and React-Native using Node
Tags: react, reactnative, node, javascript
Published: 2020-03-31 13:44:46

Dealing with Apple is a pain, ask any devs around. So I'll be writing a quick guide to setup Apple Sign in a couple of minutes (depending on Apple 🤷‍♂️)

![Apple Sign-in Gif](https://dev-to-uploads.s3.amazonaws.com/i/dncrj7q65pthl91akcf8.gif)

├── [Setting Up Developer Portal](#setting-up-developer-portal)
├── [Setting Up React.js](#setting-up-react.js)
├── [Setting Up Node](#setting-up-node)
├── [Setting Up React-Native](#setting-up-react-native)

# Setting Up Developer Portal:
First, sign in to the [Apple Developer Portal](https://developer.apple.com/account/resources/certificates/list) and enroll if you have not already, and then click on `Certificates, Identifiers and Profiles.`

![Certificates](https://dev-to-uploads.s3.amazonaws.com/i/70800u2ura23vcdkugys.png)

## Setting up App ID
From the sidebar, choose Identifiers then click the blue plus icon.

![Identifiers](https://dev-to-uploads.s3.amazonaws.com/i/pebzzd4gtff3z8nf2od7.png)

**PS.** If you have already set up your `react-native-app` you'll see a different screen. Choose the right `Bundle ID` only and go to [Adding Sign-in Capability](#add-capability).

![Select Previous AppID](https://dev-to-uploads.s3.amazonaws.com/i/4rz2up1o50qz2jemjhwt.png)

### Choose App ID
Now, Choose App IDs in this first step.

![App IDs](https://dev-to-uploads.s3.amazonaws.com/i/hxzxkaosrwkwf0xdvzcg.png)

Now, choose a description and Bundle ID for the App ID. It is best when it’s a reverse-dns style string. So my suggestion, if `www.example.com` is your web-app URL, `com.example.app` can be your Bundle ID.

![Bundle ID](https://dev-to-uploads.s3.amazonaws.com/i/rqwfj2bke3d26m3ppsiq.png)

### Add Capability
You’ll also want to scroll down through the list of capabilities and check the box next to Sign In with Apple and Enable it as Primary App ID.

![Enable Sign-In](https://dev-to-uploads.s3.amazonaws.com/i/q47qwh1a9kixrucjgi47.png)

## Setting up Service ID
Now Go ahead and create a new identifier and choose Services IDs.

![Services IDs](https://dev-to-uploads.s3.amazonaws.com/i/x5nq2grfn3cqrkvbq7sh.png)

In the next step, you’ll define the name of the app that the user will see during the login flow, as well as define the identifier which becomes the OAuth `client_id`.
You can set it as `app.example.com` as `com.example.app` is used for Bundle ID.

**PS.** Make sure to also check the Sign In with Apple checkbox.

![check the Sign In](https://dev-to-uploads.s3.amazonaws.com/i/kcwzslh2t3xovr77hhwq.png)

You’ll also need to click the Configure button next to Sign In with Apple in this step. This is where you’ll define the domain your app is running on, as well as define the redirect URLs used during the OAuth flow.

![Redirect URLs](https://dev-to-uploads.s3.amazonaws.com/i/30mdakgua8kxq6sjwkq0.png)

**PS.** Apple doesn’t allow localhost URLs in this step. You have to use a real domain here or use [Ngrok](https://ngrok.com/).

Go ahead and click Save and then Continue and Register until this step is all confirmed.

## Setting up Private Key
You actually have more steps to do generate `Key`. Either follow the guide by [Aaron Parecki](https://developer.okta.com/blog/2019/06/04/what-the-heck-is-sign-in-with-apple#create-a-private-key-for-client-authentication) or hop on:
Back in the main `Certificates, Identifiers & Profiles` screen, choose Keys from the side navigation.

![Keys](https://dev-to-uploads.s3.amazonaws.com/i/gap11bluszrqhfukzng6.png)

Click the blue plus icon to register a new key. Give your key a name, and check the Sign In with Apple checkbox.

![Sign-in checkbox](https://dev-to-uploads.s3.amazonaws.com/i/zid9emz2un0bpit99y69.png)

Click the `Configure` button and select the primary App ID you created earlier.

![Configure](https://dev-to-uploads.s3.amazonaws.com/i/ghumlkls5uu42q72ncqj.png)

Apple will generate a new private key for you and let you download it only once. Make sure you save this file because you won’t be able to get it back again later. Then, press Done.

![Download Key](https://dev-to-uploads.s3.amazonaws.com/i/zens0nqscppokrp4dgoe.png)

# Setting Up React.js:
Damn, long process right? Bare with me, now is the easier part. The first thing you need to do is add the apple script. We use `scriptjs` for that.

## Init AppleID
```js
scriptjs.get('https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js', () => {
  const params = {
    clientId: 'app.example.com',
    redirectURI: 'https://www.example.com/apple/callback',
    scope: 'name email',
  };
  window.AppleID.auth.init(params);
});
```

## SignIn AppleID
Next, create a button with `onClick` function `<button onClick={() =>  window.AppleID.auth.signIn()}/>`

![App Sign-in Button](https://dev-to-uploads.s3.amazonaws.com/i/pixcsihr9plus8qria79.png)

It'll open the Apple View, it's different on `Safari` however, but I'll be attaching the Screenshot of other browsers here:

![Sign-In Screen](https://dev-to-uploads.s3.amazonaws.com/i/qpft6bvnx200bqgb4n5t.png)

![Apple Permission Screen](https://dev-to-uploads.s3.amazonaws.com/i/fxxi15g0k6qrj9a349i5.png)

Now, after continuing, it'll forward a `POST` request 🤦‍♂️. We know to handle it inside `React/Next` is a task on its own.

# Setting Up Node:
So, we define the route `/apple/callback` on the backend and redirect to `/app` route being handled in the frontend.

```js
app.use('/apple/callback', function(req, res) {
  console.log(req.body);
});
```

## Handling JWT
We have the `id_token` and `code` inside `body`. If you want to validate the user using `code`, please follow the guide by [Arjun Komath
](https://medium.com/techulus/how-to-setup-sign-in-with-apple-9e142ce498d4) cause we'll only be using `id_token`.

You have two ways of getting the user. Since the `id_token` is just a `jwt` we can:
- Extract the JWT
- Validate the JWT

### Extract JWT
```js
const getAppleUserId = async token => {
  const parts = token.split('.');
  try {
    return await JSON.parse(new Buffer(parts[1], 'base64').toString('ascii'));
  } catch (e) {
    return null;
  }
};

app.use('/apple/callback', function(req, res) {
  const user = await getAppleUserId(req.body.id_token);
  console.log(user);
});
```

### Validate the JWT
```js
import axios from 'axios';
import NodeRSA from 'node-rsa';
import jsonwebtoken from 'jsonwebtoken';

async function _getApplePublicKeys() {
  return axios
    .request({
      method: 'GET',
      url: 'https://appleid.apple.com/auth/keys',
    })
    .then(response => response.data.keys);
}

const getAppleUserId = async token => {
  const keys = await _getApplePublicKeys();
  const decodedToken = jsonwebtoken.decode(token, { complete: true });
  const kid = decodedToken.header.kid;
  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'
  );
  const userKey = pubKey.exportKey(['public']);

  return jsonwebtoken.verify(token, userKey, {
    algorithms: 'RS256',
  });
};

app.use('/apple/callback', function(req, res) {
  const user = await getAppleUserId(req.body.id_token);
  console.log(user);
});
```

Where, 
- `_getApplePublicKeys` is just fetching the multiple keys from `https://appleid.apple.com/auth/keys`. 
- Decode the `id_token` to extract the kid and extract the exact key which matches the `kid` of `id_token`.
- Build the RSA and verify the `jwt`.

## Forwarding to Frontend
Then, you can forward the user details by sending the data as:

```js
app.use('/apple/callback', function(req, res) {
  const user = await getAppleUserId(req.body.id_token);
  res.redirect(303, 'https://www.example.com/app?user=${JSON.stringify(req.body.id_token)}');
});
```

Now, define a route `/app` inside `react.js` and then, on `useEffect` or `componentDidMount` just use `query-string` to get the `user`.

```js
const user = queryString.parse(location.search).user;
console.log(JSON.parse(user));
```

**PS.** You can also set the cookie on the backend and parse it using `react-cookie`. 

# Setting Up React-Native:
It's fairly simple on the `react-native` though with the introduction of [react-native-apple-authentication](https://github.com/invertase/react-native-apple-authentication) package.

You can just follow the [Initial-Setup Guide](https://github.com/invertase/react-native-apple-authentication/blob/master/docs/INITIAL_SETUP.md) or hop on:

## Add Sign-in capability
Taking into consideration that you already have Target setup on `XCode` with bundle id `com.example.app`, just add the `Sign In with Apple` capability inside:

![Sign In with Apple capability](https://dev-to-uploads.s3.amazonaws.com/i/1nu6ropghscybxbph4a2.png)

**PS.** You need a valid team, however, once you set it you will see a screen similar to this:

![Capability Success](https://dev-to-uploads.s3.amazonaws.com/i/ceoufct23yr3mpcjngb9.png)

Now, if you have not followed the guide from above you need to have your `AppID` setup in `Identifiers`. Follow the guide above for [AppID](#setting-up-app-id) only and come back here.

**Note:** Enable the APP ID as a primary if not already and click the Save button.

Now that you have everything set up, just add the package:
```js
yarn add @invertase/react-native-apple-authentication
```

And then use the `AppleButton` where you can add custom-styles as well. On pressing the button, we call a function to extract the `identityToken` with scope of for `email` and `name`.

```js
import { Platform } from 'react-native';
import appleAuth, {
  AppleButton,
  AppleAuthRequestScope,
  AppleAuthRequestOperation,
} from '@invertase/react-native-apple-authentication';

const appleAuth = () => {
  const token = appleAuth
    .performRequest({
      requestedOperation: AppleAuthRequestOperation.LOGIN,
      requestedScopes: [
        AppleAuthRequestScope.EMAIL,
        AppleAuthRequestScope.FULL_NAME,
      ],
    })
    .then(
      res => {
        return res.identityToken;
      },
      error => {
        console.log(error);
      }
    );
  console.log(token);
  // TODO: Send the token to backend
};

export const appleButton = () => {
  return (
    <>
      {Platform.OS === 'ios' && (
        <AppleButton
          buttonType={AppleButton.Type.CONTINUE}
          onPress={() => appleAuth()}
        />
      )}
    </>
  );
};
```

Now that we have the token, we send it to our `node-backend`. However, [above](#setting-up-node) we are handling for the condition of `callback_url` and `POST Request`. Here, we'll use the same logic of `getAppleUserId`, so we'll just send the token to `/apple/login` backend route as:

```js
import HttpStatus from 'http-status-codes';

app.use('/apple/login', function(req, res) {
  const user = await getAppleUserId(req.body.token);
  res.status(HttpStatus.OK).json({ user });
});
```

Where,
- `getAppleUserId` is defined [above](#handling-jwt). Just have a look at it where we either `extract` or `validate` the `jwt` and extract the data. 

Next, we forward the user back to `react-native-client`.

# Conclusion
Setting it up is definitely not easy and requires patience. It took a lot of time for us as well and we intend to solve it for you. There might be some grammatical mistakes or logic issues here and there, do let me know if you find it.

Thanks for giving this a read. We will continue to keep you posted on the updates and cool stuff.

Until next time 👋

Sunim - https://prep.surf/blog/new-landing-page