engineering

Implement Sign In with Apple in React Native

Implement Sign In with Apple in React Native

A tutorial on how to enable Apple Sign-In in React Native. Tutorial covers UI, User validation, and sessions for a secure and user-friendly authentication

Vaishnav Chandurkar

Vaishnav Chandurkar

Apr 25, 2024 7 min read

Almost all apps we use these days have SSO (Single Sign-On) options like Google, Facebook, and Github. Similarly, we have Login with Google for the Peerlist mobile app. Now as per the Apple guidelines, we need to add Sign In with Apple to our app for App Store release.

We already have an article covering Sign-in with Google.

Let's get started.

Table of Content

Initial development setup

If you don't have a react-native project setup, you can create one using following command:

npx react-native init HelloWorld

Open this project in Xcode.

To add Sign In with Apple we need to do some configuration in Xcode and in Apple Developer Account.

Xcode configuration Firstly we need to add a new capability, Sign in with Apple to our app. Follow the following setups

  1. Click on the app target
  2. Select Signing & Capabilities
  3. Click on + Capability & select Sign in with Apple
Select Signing & Capabilities
Select Signing & Capabilities. Source: Apple

Select Sign In with Apple
Select Sign In with Apple. Source: Apple

Apple Developer account configuration

  1. Head over to the https://developer.apple.com/account and select Identifiers
  2. Click on your project in the list.
  3. Find Sign In with Apple and click on Edit and then the Save button at the top of the screen.
  4. Now go back and click on Key from the left sidebar and create a new key.
  5. Give the key name & click on the Sign In with the Apple checkbox and click Configure.
  6. Select HelloWorld as our primary app ID and save.
  7. Register your key.

Now that we have capability added to our app, Let's talk about basic sign-in flow.

Client-side

  1. Users select sign-in providers like Google, Apple, and Facebook.
  2. User grant permission required by the app.
  3. Provider redirects the user to the app with the token.
  4. App(client) sends this token to the backend for further processing.

Backend

  1. The backend validates the token with the provider and retrieves information from the provider.
  2. The app uses this information to Sign In or Create a new account for the user.

Let's move towards the code part.

Implementing Apple Authentication

Firstly we need to install one package that will trigger Apple's native sign-in flow.

npm install @react-native-community/apple-authentication

we also need to do cd ios and pod install to update pod files.

To provide users with the option to Sign In with Apple, we will need a button on the UI. Here is the basic Sign In button setup. You can customise it as required.

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

export default function App() {
	const [loading, setLoading] = useState(false);
	const [error, setError] = useState('');

	const handleAppleLogin = async () => {
		setLoading(true);
		try {
			// start login request
			const response = await appleAuth.performRequest({
				requestedOperation: appleAuth.Operation.LOGIN,
				requestedScopes: [appleAuth.Scope.EMAIL],
			});

			if (response?.identityToken) {
				const resp = await authAPI.validateAppleToken({
					token: response.identityToken,
				});
				await handlePostLoginData(resp.data);
			}
		} catch (err) {
			setError(err?.response?.data?.message || 'Something went wrong.');
		} finally {
			setLoading(false);
		}
	};

	return (
		<View>
			<Pressable onPress={handleAppleLogin}>Continue with Apple</Pressable>
		</View>
	);
}

Handle Apple Sign-In

In the handleAppleLogin function, we are performing 2 steps. Sign In and validate the identityToken.

In step 1 We will attempt to sign in with Apple when the button is pressed. This function performs the following steps:

  • Initiates the Apple Sign-In process using appleAuth.performRequest({..}).
  • If successful, it returns data, which includes email, fullName, nonce, identityToken

If you are facing any specific issue you can check the documentation of the react-native-apple-authentication package.

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.

In step 2 We will send the identityToken we received from Apple and send it to our backend for validation. But before saving or returning user data to the client we need to verify that identityToken was kept the same along the way.

How to verify identityToken

To verify identityToken from the client, we need Apple's public key to verify the signature. We can get the public key from the following endpoint:

GET https://appleid.apple.com/auth/keys

The response would look like this:

{
	"keys": [
		{
			"kty": "RSA",
			"kid": "pyaRQpAbnY",
			"use": "sig",
			"alg": "RS256",
			"n": "qHiwOpizi6xHG8FIOSWH4l0P1CjLIC7aBFkhbk7BrD4s9KQAs5Sj5xAtOwlZMyP2XFcqRtZBLIMM7vw_CNERtRrhc68se5hQE_vsrHy7ugcQU6ogJS6s54zqO-zTUfaa3mABM6iR-EfgSpvz33WTQZAPtwAyxaSLknHyDzWjHEZ44WqaQBdcMAvgsWMYG5dBfnV-3Or3V2r1vdbinRE5NomE2nsKDbnJ3yo3u-x9TizKazS1JV3umt71xDqbruZLybIrimrzg_i9OSIzT2o5ZWz8zdYkKHZ4cvRPh-DDt8kV7chzR2tenPF2c5WXuK-FumOrjT7WW6uwSvhnhwNZuw",
			"e": "AQAB"
		},
		{
			"kty": "RSA",
			"kid": "lVHdOx8ltR",
			"use": "sig",
			"alg": "RS256",
			"n": "nXDu9MPf6dmVtFbDdAaal_0cO9ur2tqrrmCZaAe8TUWHU8AprhJG4DaQoCIa4UsOSCbCYOjPpPGGdE_p0XeP1ew55pBIquNhNtNNEMX0jNYAKcA9WAP1zGSkvH5m39GMFc4SsGiQ_8Szht9cayJX1SJALEgSyDOFLs-ekHnexqsr-KPtlYciwer5jaNcW3B7f9VNp1XCypQloQwSGVismPHwDJowPQ1xOWmhBLCK50NV38ZjobUDSBbCeLYecMtsdL5ZGv-iufddBh3RHszQiD2G-VXoGOs1yE33K4uAto2F2bHVcKOUy0__9qEsXZGf-B5ZOFucUkoN7T2iqu2E2Q",
			"e": "AQAB"
		},
		{
			"kty": "RSA",
			"kid": "Bh6H7rHVmb",
			"use": "sig",
			"alg": "RS256",
			"n": "2HkIZ7xKMUYH_wtt2Gwq6jXKRl-Ng5vdwd-XcWn5RIW82-uxdmGJyTo3T6MPty-xWUeW7FCs9NlM4yu02GKgwep7TKfnOovP78sd3rESbZsvuN7zD_Vk6aZP7QfqblElUtiMQxh9bu-gZUeMZfa_ndX-P5C4yKtZvXGrSPLLjyAcSmSHNLZnWbZXjeIVsgXWHMr5fwVEAkftHq_4py82xgn2XEK0FK9HmWOCZ47Wcx9fWBnqSi9JTJTUX0lh-kI5TcYfv9UKX2oe3uyOn-A460E_L_4ximlM-lgi3otw26EZfAGY9FFgSZoACjhgw_z5NRbK9dycHRpeLY9GxIyiYw",
			"e": "AQAB"
		}
	]
}

This response is JWKS. JWKS stands for JSON Web Key (JWK) Set. It is a set of keys containing the public keys that we can use to verify JWT. It's difficult to write specific code to get a public key from JWKS. so for this, we can rely on libraries to do the job.

For Node.Js we can use jwks-rsa. For other programming languages, you can search for JWKS to the public key, and choose library as the programming language of your choice.

Now that we have the JSON Web Key Set, we need to select the right key from the response to validate identityToken. If you header over to the jwt.ioi and decode identityToken, you will see something like this.

{
	"kid": "Bh6H7rHVmb",
	"alg": "RS256"
}

You will need kid to identify the right key from the JWKS.

The "kid" (key ID) Header Parameter is a hint indicating which key was used to secure the JWS.

Retrieving key from JWKS

Let's install jwks-rsa which will do the heavy work for us.

npm i jwks-rsa

Let's write a service to fetch the JWK set from Apple and validate our identityToken.

import jwksClient from 'jwks-rsa';
import jwt from 'jsonwebtoken';

const APPLE_BASE_URL = 'https://appleid.apple.com';
const JWKS_APPLE_URI = '/auth/keys';

const validateToken = async (identityToken) => {
	const decodedToken = jwt.decode(identityToken, { complete: true });
	const { kid, alg } = decodedToken.header;

	// fetch JSON web token key set
	const client = jwksClient({ jwksUri: `${APPLE_BASE_URL}/${JWKS_APPLE_URI}` });
	const signingKey = await client.getSigningKey(kid);

	const publicKey = signingKey.getPublicKey();
	const publicKid = signingKey.kid;
	const publicAlg = signingKey.alg;

	if (publicAlg !== alg) {
		throw new Error('the alg does not match with jwk config.');
	}
	if (publicKid !== kid) {
		throw new Error('the kid does not match with Apple auth keys');
	}

	const token = jwt.verify(identityToken, publicKey, {
		algorithms: publicAlg,
	});

	if (token?.iss !== APPLE_BASE_URL) {
		throw new Error('the iss does not match with Apple.');
	}
	return token;
};

Let's understand what's happening.

  1. Firstly we decode our identityToken with jwt.decode to get the kid (key id) and alg (algorithm).
  2. Using jwksClient we request the Apple endpoint to get a JSON web key set.
  3. Using kid we get the right signingKey from the JSON Web Key (JWK) Set.
  4. We can do simple validation for kid and alg fileds.
  5. Now let's verify identityToken with publicAlg and publicAlg. After you validate that identityToken hasn't tempered, now you can validate data inside.

Let's log the token.

{
	"iss": "https://appleid.apple.com",
	"aud": "com.youApplication.name",
	"exp": 1713937194,
	"iat": 1713850794,
	"sub": "000152.c76a060e97c64551a3dd7b879ef0a6d3.0847",
	"nonce": "6e4c1e5775c979528013e792ab6e5068c2da51bc8a73313af6d8cde7bac33830",
	"c_hash": "FemM6SKtqDZ8Jqw1i1uLRw",
	"email": "[email protected]",
	"email_verified": true,
	"auth_time": 1713850794,
	"nonce_supported": true
}

Now using this token data we can find the user in the database and continue with Login or Create a new account as per requirement. To maintain user sessions in your React Native app, you can use techniques like JWT (JSON Web Tokens) or secure sessions. These mechanisms help keep track of user logins and provide a seamless user experience. In the Peerlist app implementation, we attached the session to the user’s profile hence you can do something similar.

Bonus: React Native Jobs

If you are looking for a job in React Native, check out these jobs on Peerlist. You can also post your job openings for free!

Final thoughts

Implementing Sign In with Apple in React Native enhances user experience by providing a secure authentication option. This guide covers setup in Xcode, backend validation, and key retrieval from JWKS. By following these steps, you can seamlessly integrate this feature.

If you are facing any issue, you can message me at peerlist.io/vaishnavs.