Skip to content
/Implementing Sign In with Apple in React Native (iOS + Android)
Mar 2025·7 min read

Implementing Sign In with Apple in React Native (iOS + Android)

A complete walkthrough of integrating Sign In with Apple using @invertase/react-native-apple-authentication — covering Xcode capability setup, Android workaround via JWT, private email relay, credential state handling, and App Store compliance gotchas.

React NativeAuthenticationiOSAppleSecurity
Text-to-Speech unsupported
Text Size

Apple Sign In is not optional — if your app offers any third-party login (Google, Facebook, etc.), Apple mandates you also offer Sign In with Apple or it gets rejected from the App Store. I learned this the hard way during a review cycle on a client project. This post documents every step from Xcode capability setup to credential state monitoring, plus the often-ignored Android fallback that most guides skip entirely.

Why Sign In with Apple is different

Unlike Google or Facebook OAuth, Apple Sign In has a few unique behaviours you must design around: Apple only sends the user's name and email on the very first login. If the user revokes and re-authorises, you get the user ID but not the name/email again — ever. You must persist name and email immediately on first sign-in, not on a subsequent profile fetch. Apple also offers a private email relay (e.g. abc123@privaterelay.appleid.com) that forwards to the user's real inbox. Your backend must be whitelisted to send to these relay addresses via Apple's developer portal.

Step 1 — Xcode capability and Apple Developer setup

In Xcode, open your project → select the target → go to Signing & Capabilities → click the + button → add Sign In with Apple. This creates an entitlement file automatically. In the Apple Developer portal, go to Identifiers → select your App ID → enable Sign In with Apple → configure the primary App ID if you're using an extension. For backend token validation, create a Services ID (used as the client_id for web flows) and generate a Key (.p8 file) under Keys — this is your client secret material for the REST API.

Step 2 — Install the library

terminal
npm install @invertase/react-native-apple-authentication
# iOS
cd ios && pod install
# No additional linking needed for RN 0.63+

Step 3 — Trigger the Sign In flow

AppleSignInButton.jsx
import appleAuth, {
  AppleButton,
  AppleAuthRequestOperation,
  AppleAuthRequestScope,
  AppleAuthCredentialState,
} from '@invertase/react-native-apple-authentication';
import { Alert } from 'react-native';

export default function AppleSignInButton() {
  async function handleAppleSignIn() {
    try {
      const appleAuthRequestResponse = await appleAuth.performRequest({
        requestedOperation: AppleAuthRequestOperation.LOGIN,
        requestedScopes: [
          AppleAuthRequestScope.EMAIL,
          AppleAuthRequestScope.FULL_NAME,
        ],
      });

      const { user, email, fullName, identityToken, nonce } =
        appleAuthRequestResponse;

      // CRITICAL: persist name/email immediately — Apple won't send again
      const displayName =
        fullName?.givenName && fullName?.familyName
          ? `${fullName.givenName} ${fullName.familyName}`
          : 'Apple User';

      // Send identityToken + nonce to your backend for JWT verification
      await sendToBackend({ identityToken, nonce, user, email, displayName });

    } catch (error) {
      if (error.code === '1001') return; // user cancelled — not an error
      Alert.alert('Sign In Failed', error.message);
    }
  }

  if (!appleAuth.isSupported) return null;

  return (
    <AppleButton
      buttonStyle={AppleButton.Style.WHITE}
      buttonType={AppleButton.Type.SIGN_IN}
      style={{ width: 200, height: 44 }}
      onPress={handleAppleSignIn}
    />
  );
}

Step 4 — Backend JWT verification

Never trust the identityToken on the client side alone. Send the identityToken and nonce to your server. On the backend, fetch Apple's public keys from https://appleid.apple.com/auth/keys, verify the JWT signature using the matching key, check the nonce matches, confirm the iss is https://appleid.apple.com, and confirm the aud matches your Bundle ID. Libraries like apple-signin-auth (Node.js) or python-jose handle this cleanly. Only after verification create or update the user record.

Step 5 — Monitor credential state changes

useAppleCredentialListener.js
import { useEffect } from 'react';
import appleAuth, {
  AppleAuthCredentialState,
} from '@invertase/react-native-apple-authentication';
import { useDispatch } from 'react-redux';
import { signOut } from '../store/authSlice';

export function useAppleCredentialListener(userId) {
  const dispatch = useDispatch();

  useEffect(() => {
    if (!userId || !appleAuth.isSupported) return;

    // Fires when user revokes Apple Sign In from iOS Settings
    const unsubscribe = appleAuth.onCredentialRevoked(async () => {
      const state = await appleAuth.getCredentialStateForUser(userId);
      if (state === AppleAuthCredentialState.REVOKED) {
        dispatch(signOut());
      }
    });

    return () => unsubscribe();
  }, [userId]);
}

Android — the forgotten step

Apple Sign In on Android requires a web-based OAuth flow through a Services ID (not the App ID). You redirect the user to Apple's auth URL in a WebView or browser, receive a code + id_token via your redirect URI (must be HTTPS), and exchange it on your backend. The @invertase library supports this via appleAuthAndroid module. Key difference: you must generate and verify a nonce client-side as a SHA256 hash, pass it raw to Apple, and verify the hashed version in the JWT. Skipping the nonce check is a security hole.

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

async function handleAndroidAppleSignIn() {
  const rawNonce = uuid();
  const state = uuid();

  appleAuthAndroid.configure({
    clientId: 'com.yourcompany.yourapp.service', // Services ID
    redirectUri: 'https://yourapp.com/auth/apple/callback',
    responseType: appleAuthAndroid.ResponseType.ALL,
    responseMode: appleAuthAndroid.ResponseMode.QUERY,
    scope: appleAuthAndroid.Scope.ALL,
    nonce: rawNonce,
    state,
  });

  const response = await appleAuthAndroid.signIn();
  // Send response.id_token + rawNonce to backend for verification
}

Three things that will get your app rejected: not offering Apple Sign In when you offer other social logins, not handling the credential revocation listener, and sending marketing emails to Apple's private relay without whitelisting your domain in the Apple Developer portal. Check all three before submitting.

Share X LinkedIn
NY
Neelesh Yadav
React Native Developer with 3 yr 6 mos years building production mobile apps. Writes about performance, architecture, and mobile engineering.
Follow ↗

Comments

Leave a comment

Comments are reviewed before they appear.

More Articles

Boosting React Native Performance: New Architectures Deep Dive
8 min read
Building Real-Time Chat with WebSocket in React Native
6 min read
AR Try-On: Three.js + MediaPipe in React Native
10 min read