npm package: @invertase/react-native-apple-authentication
Description: A complete Apple Authentication services API for React Native iOS apps.
Author: Invertase
Latest version: 2.5.1

<p align="center">
  <h2 align="center">React Native Apple Authentication</h2>
</p>

<p align="center">
  <a href="https://www.npmjs.com/package/@invertase/react-native-apple-authentication"><img src="https://img.shields.io/npm/dm/@invertase/react-native-apple-authentication.svg?style=flat-square" alt="NPM downloads"></a>
  <a href="https://www.npmjs.com/package/@invertase/react-native-apple-authentication"><img src="https://img.shields.io/npm/v/@invertase/react-native-apple-authentication.svg?style=flat-square" alt="NPM version"></a>
  <a href="/LICENSE"><img src="https://img.shields.io/npm/l/react-native-firebase.svg?style=flat-square" alt="License"></a>
</p>

<p align="center">
  <a href="https://invertase.link/discord"><img src="https://img.shields.io/discord/295953187817521152.svg?style=flat-square&colorA=7289da&label=Chat%20on%20Discord" alt="Chat on Discord"></a>
  <a href="https://twitter.com/invertaseio"><img src="https://img.shields.io/twitter/follow/invertaseio.svg?style=flat-square&colorA=1da1f2&colorB=&label=Follow%20on%20Twitter" alt="Follow on Twitter"></a>
</p>

---

A well typed React Native library providing support for Apple Authentication on iOS and Android, including support for all `AppleButton` variants.

![apple-auth](https://static.invertase.io/assets/apple-auth.png)

## Prerequisites to using this library

The `@invertase/react-native-apple-authentication` library will not work if you do not ensure the following:

- You are using React Native version `0.60` or higher.

- (iOS only) You have setup react-native iOS development environment on your machine (Will only work on Mac). If not, please follow the official React Native documentation for getting started: [React Native getting started documentation](https://facebook.github.io/react-native/docs/getting-started).

- (iOS only) You are using Xcode version `11` or higher. This will allow you to develop using iOS version `13` and higher, when the APIs for Sign In with Apple became available.

- **Once you're sure you've met the above, please follow our [Initial development environment setup](docs/INITIAL_SETUP.md) guide.**

## Version 2.0.0 breaking changes

Version 2 added Android support and introduced a few breaking changes with how methods are accessed. Please see the [Migration Guide](docs/MIGRATION.md).

## Installation

```bash
yarn add @invertase/react-native-apple-authentication

(cd ios && pod install)
```

You will not have to manually link this module as it supports React Native auto-linking.

### Expo usage

To enable the Sign In with Apple capability in your app, Apple requires
you to add an entitlement. You may configure this in your `app.json`

```json
{
  "ios": {
    "entitlements": {
      "com.apple.developer.applesignin": ["Default"]
    }
  }
}
```

You may also need to run `npx expo prebuild`.

## Usage

Below are simple steps to help you get up and running. The implementation differs between iOS an Android, so if you're having trouble, be sure to look through the docs. Please skip and head to the full code examples noted below if you prefer to see a more complete implementation:

- [React Hooks example (iOS)](example/app.ios.js)
- [React Class example (iOS)](example/classVersion.js)
- [React Hooks example (Android)](example/app.android.js)
- [Auth0 Implementation](docs/Auth0.md)
- If you're authenticating users via `React Native Firebase`; see our [Firebase guide](docs/FIREBASE.md)
- For Android support, a couple extra steps are required on your Apple developer account. Checkout [our guide](docs/ANDROID_EXTRA.md) for more info.

### iOS

#### 1. Initial set-up

Import the `appleAuth` ([API documentation](typedocs/variables/appleAuth.md)) module and the `AppleButton` ([API documentation](typedocs/variables/AppleButton.md)) exported member element from the `@invertase/react-native-apple-authentication` library. Setup an event handler (`onPress`) to kick start the authentication request.

```js
// App.js

import React from 'react';
import { View } from 'react-native';
import { AppleButton } from '@invertase/react-native-apple-authentication';

async function onAppleButtonPress() {

}

function App() {
  return (
    <View>
      <AppleButton
        buttonStyle={AppleButton.Style.WHITE}
        buttonType={AppleButton.Type.SIGN_IN}
        style={{
          width: 160, // You must specify a width
          height: 45, // You must specify a height
        }}
        onPress={() => onAppleButtonPress()}
      />
    </View>
  );
}
```

#### 2. Implement the login process

```js
// App.js

import { appleAuth } from '@invertase/react-native-apple-authentication';

async function onAppleButtonPress() {
  // performs login request
  const appleAuthRequestResponse = await appleAuth.performRequest({
    requestedOperation: appleAuth.Operation.LOGIN,
    // Note: it appears putting FULL_NAME first is important, see issue #293
    requestedScopes: [appleAuth.Scope.FULL_NAME, appleAuth.Scope.EMAIL],
  });

  // get current authentication state for user
  // /!\ This method must be tested on a real device. On the iOS simulator it always throws an error.
  const credentialState = await appleAuth.getCredentialStateForUser(appleAuthRequestResponse.user);

  // use credentialState response to ensure the user is authenticated
  if (credentialState === appleAuth.State.AUTHORIZED) {
    // user is authenticated
  }
}
```

#### 3. Event Listener

Set up event listener for when user's credentials have been revoked.

```js
// App.js

import React, { useEffect } from 'react';
import { View } from 'react-native';
import { appleAuth, AppleButton } from '@invertase/react-native-apple-authentication';

function App() {
  useEffect(() => {
    // onCredentialRevoked returns a function that will remove the event listener. useEffect will call this function when the component unmounts
    return appleAuth.onCredentialRevoked(async () => {
      console.warn('If this function executes, User Credentials have been Revoked');
    });
  }, []); // passing in an empty array as the second argument ensures this is only ran once when component mounts initially.

  return (
    <View>
      <AppleButton onPress={() => onAppleButtonPress()} />
    </View>
  );
}
```

#### 4. Implement the logout process

There is an operation `appleAuth.Operation.LOGOUT`, however it does not work as expected and is not even being used by Apple in their example code. See [this issue for more information](https://github.com/invertase/react-native-apple-authentication/issues/10#issuecomment-611532131)

So it is recommended when logging out to just clear all data you have from a user, collected during `appleAuth.Operation.LOGIN`.

### Android

#### 1. Initial set-up

Make sure to correctly configure your Apple developer account to allow for proper authentication on Android. You can checkout [our guide](docs/ANDROID_EXTRA.md) for more info.

```js
// App.js

import React from 'react';
import { View } from 'react-native';
import { appleAuthAndroid, AppleButton } from '@invertase/react-native-apple-authentication';

async function onAppleButtonPress() {
}

// Apple authentication requires API 19+, so we check before showing the login button
function App() {
  return (
    <View>
      {appleAuthAndroid.isSupported && (
        <AppleButton
          buttonStyle={AppleButton.Style.WHITE}
          buttonType={AppleButton.Type.SIGN_IN}
          onPress={() => onAppleButtonPress()}
        />
      )}
    </View>
  );
}
```

#### 2. Implement the login process

```js
// App.js

import { appleAuthAndroid } from '@invertase/react-native-apple-authentication';
import 'react-native-get-random-values';
import { v4 as uuid } from 'uuid'

async function onAppleButtonPress() {
  // Generate secure, random values for state and nonce
  const rawNonce = uuid();
  const state = uuid();

  // Configure the request
  appleAuthAndroid.configure({
    // The Service ID you registered with Apple
    clientId: 'com.example.client-android',

    // Return URL added to your Apple dev console. We intercept this redirect, but it must still match
    // the URL you provided to Apple. It can be an empty route on your backend as it's never called.
    redirectUri: 'https://example.com/auth/callback',

    // The type of response requested - code, id_token, or both.
    responseType: appleAuthAndroid.ResponseType.ALL,

    // The amount of user information requested from Apple.
    scope: appleAuthAndroid.Scope.ALL,

    // Random nonce value that will be SHA256 hashed before sending to Apple.
    nonce: rawNonce,

    // Unique state value used to prevent CSRF attacks. A UUID will be generated if nothing is provided.
    state,
  });

  // Open the browser window for user sign in
  const response = await appleAuthAndroid.signIn();

  // Send the authorization code to your backend for verification
}
```

### MacOS

This library works on MacOS 10.15+ if using in conjunction with [react-native-macos](https://github.com/microsoft/react-native-macos).

### Web (not react-native-web, but that may come as a follow-on, this is pure web at the moment)

#### 1. Initial set-up

- Ensure you follow the android steps above.
- Install the [web counterpart](https://github.com/A-Tokyo/react-apple-signin-auth) `yarn add react-apple-signin-auth` in your web project.

#### 2. Implement the login process on web

```js
import AppleSignin from 'react-apple-signin-auth';

/** Apple Signin button */
const MyAppleSigninButton = ({ ...rest }) => (
  <AppleSignin
    /** Auth options passed to AppleID.auth.init() */
    authOptions={{
      clientId: 'SAME AS ANDROID',
      redirectURI: 'SAME AS ANDROID',
      scope: 'email name',
      state: 'state',
      /** sha256 nonce before sending to apple to unify with native firebase behavior - https://github.com/invertase/react-native-apple-authentication/issues/28 */
      nonce: sha256('nonce'),
      /** We have to usePopup since we need clientSide authentication */
      usePopup: true,
    }}
    onSuccess={(response) => {
      console.log(response);
      // {
      //     "authorization": {
      //       "state": "[STATE]",
      //       "code": "[CODE]",
      //       "id_token": "[ID_TOKEN]"
      //     },
      //     "user": {
      //       "email": "[EMAIL]",
      //       "name": {
      //         "firstName": "[FIRST_NAME]",
      //         "lastName": "[LAST_NAME]"
      //       }
      //     }
      // }
    }}
  />
);

export default MyAppleSigninButton;
```

#### 3. Verify serverside

- Send the apple response to your server.
- See [Serverside Verification](#serverside-verification)
- Ensure that you pass the clientID as the web service ID, not the native app bundle. Since the project utilizes the service ID for authenticating web and android.

## Serverside verification

#### Nonce

- Based on the [Firebase implementation guidelines](https://firebase.google.com/docs/auth/ios/apple#sign_in_with_apple_and_authenticate_with_firebase) the nonce provided to `appleAuth.performRequest` (iOS) and `appleAuthAndroid.configure` (Android) is automatically SHA256-hashed.
- To verify the nonce serverside you first need to hash the nonce value, ie:

  ```js
  crypto.createHash('sha256').update(nonce).digest('hex');
  ```

- The nonce can then be easily compared serverside for extra security verification, ie:

  ```js
  import crypto from 'crypto';
  import appleSigninAuth from 'apple-signin-auth';

  appleIdTokenClaims = await appleSigninAuth.verifyIdToken(id_token, {
    /** sha256 hex hash of raw nonce */
    nonce: nonce ? crypto.createHash('sha256').update(nonce).digest('hex') : undefined,
  });
  ```

## API Reference Documentation

All API documentation is generated by typedoc, and [is available in the `typedocs` folder](typedocs/modules.md)

## FAQs

1. Why does `full name` and `email` return `null`?
   - Apple only returns the `full name` and `email` on the first login, it will return `null` on the succeeding login so you need to save those data.
   - Apple APIs [appear to be sensitive to the order you place the scopes in the auth request](https://github.com/invertase/react-native-apple-authentication/issues/293). Place first name *first*, like this:

   ```javascript
    const appleAuthRequestResponse = await appleAuth.performRequest({
      requestedOperation: appleAuth.Operation.LOGIN,
      requestedScopes: [appleAuth.Scope.FULL_NAME, appleAuth.Scope.EMAIL],
    });
   ```

   - For testing purposes, to be receive these again, go to your device settings; `Settings > Apple ID, iCloud, iTunes & App Store > Password & Security > Apps Using Your Apple ID`, tap on your app and tap `Stop Using Apple ID`. You can now sign-in again and you'll receive the `full name` and `email.
   - Keep in mind you can always access the `email` property server-side by inspecting the `id_token` returned from Apple when verifying the user.

2. How to change button language? (iOS)
    - Native Apple Button component reads language value from CFBundleDevelopmentRegion at Info.plist file. By changing CFBundleDevelopmentRegion value you can change default language for component.

    ```XML
    <key>CFBundleDevelopmentRegion</key>
    <string>en</string>
    ```

    - For supporting multi language, you can add CFBundleAllowMixedLocalizations key to Info.plist.

    ```XML
    <key>CFBundleAllowMixedLocalizations</key>
    <string>true</string>
    ```

3. How do I get the email after the first login?
    - You can get the email address by parsing the JWT token that's returned from any authentication, like so:

    ```js
    import { appleAuth } from '@invertase/react-native-apple-authentication';
    import { jwtDecode } from 'jwt-decode';

    const appleAuthRequestResponse = await appleAuth.performRequest({
      requestedOperation: appleAuth.Operation.LOGIN,
      requestedScopes: [appleAuth.Scope.EMAIL, appleAuth.Scope.FULL_NAME]
    });
    // other fields are available, but full name is not
    const { email, email_verified, is_private_email, sub } = jwtDecode(appleAuthRequestResponse.identityToken)
    ```

## Troubleshooting

```
The operation couldn’t be completed. (com.apple.AuthenticationServices.AuthorizationError error 1000.)
```

###### Case 1

Check that the connection settings have been made correctly.
The setup can be found here: [Initial Setup](https://github.com/invertase/react-native-apple-authentication/blob/main/docs/INITIAL_SETUP.md)

###### Case 2

If you are using the function `getCredentialStateForUser` on a simulator, this error will always be triggered, for the reason that this function verifies the authenticity of the device.

You must test your code on a real device.

###### Case 3

If you are using a simulator, go to [Manage Apple Account](https://appleid.apple.com/account/manage).

Search for "Devices", select "Simulator" and press "Remove from Account".

  ![show-devices](https://raw.githubusercontent.com/invertase/react-native-apple-authentication/main/docs/images/devices-list.jpg)

  ![remove-from-manager](https://raw.githubusercontent.com/invertase/react-native-apple-authentication/main/docs/images/remove-simulator-devices-list.jpg)

It should work fine.

```
"invalid_client" in Android webview
```

Make sure to read the Android [services setup docs](docs/ANDROID_EXTRA.md).

###### Case 1

The `clientId` you passed to `appleAuthAndroid.configure` doesn't match the Service ID you setup in your Apple developer console.

###### Case 2

Your Service ID is attached to the wrong Primary App ID, and therefore uses the incorrect Sign In with Apple key.

###### Case 3

The `redirectUri` you passed to `appleAuthAndroid.configure` doesn't match one of the return URLs or domains/subdomains you added in your Apple developer console. The URL must match *exactly*, and cannot contain a query string.

## License

- See [LICENSE](/LICENSE)

## Example app construction

This section is intended for people maintaining the module.

`react-native-test-app` is used for the example, to refresh it this is the general path:

- use yarn to update the react-native-test-app package in the package.json
- move the existing example app(s) to a side space (e.g., example-OLD)
- create the main test app (for android and ios, which are current)
- `yarn init-test-app`
  - name the app `RNAppleAuthExample`
  - choose ios and android only as the platforms (the others lag in versions)
  - create the new project in `example` directory
  - `touch example/yarn.lock` so that yarn will consider it to be separate
  - copy old example files to new example:
    - the old app.json (it sets up single-app mode and the apple entitlements)
    - the old index.*and app.* images and ios/example.entitlements files
    - remove the App.tsx file
  - add our package as a dependency '"@invertase/react-native-apple-auth": "portal:../"'
  - other packages `yarn add react-native-get-random-values base-64 jwt-decode`
  - add the `path` require and our package dir to watch folders in metro.config.js as
    it was in the old example

- Now make a second one, but for the platforms which lag behind mainline react-native:
  - `yarn init-test-app --version 0.79`, name RNAppleAuthExample, example-other
  - now do the same steps as above
  - the index.ios.js and app.ios.js files need to be copied to *.macos.*

---

<p align="center">
  <a href="https://invertase.io/?utm_source=readme&utm_medium=footer&utm_campaign=react-native-firebase">
    <img width="75px" src="https://static.invertase.io/assets/invertase/invertase-rounded-avatar.png">
  </a>
  <p align="center">
    Built and maintained by <a href="https://invertase.io/?utm_source=readme&utm_medium=footer&utm_campaign=react-native-firebase">Invertase</a>.
  </p>
</p>