Skip to main content

expo-apple-authentication

Version: 55.0.6 Provides ‘Sign in with Apple’ capability for Expo and React Native apps. Allows users to authenticate using their Apple ID with Face ID or Touch ID on iOS 13+.

Installation

npx expo install expo-apple-authentication

Usage

import * as AppleAuthentication from 'expo-apple-authentication';
import { View, StyleSheet } from 'react-native';

export default function App() {
  const handleAppleSignIn = async () => {
    try {
      const credential = await AppleAuthentication.signInAsync({
        requestedScopes: [
          AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
          AppleAuthentication.AppleAuthenticationScope.EMAIL,
        ],
      });
      
      // Signed in successfully
      console.log('User ID:', credential.user);
      console.log('Email:', credential.email);
      console.log('Name:', credential.fullName);
    } catch (e: any) {
      if (e.code === 'ERR_CANCELED') {
        // User canceled
      } else {
        // Other error
      }
    }
  };

  return (
    <View style={styles.container}>
      <AppleAuthentication.AppleAuthenticationButton
        buttonType={AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN}
        buttonStyle={AppleAuthentication.AppleAuthenticationButtonStyle.BLACK}
        cornerRadius={5}
        style={styles.button}
        onPress={handleAppleSignIn}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
  button: { width: 200, height: 44 },
});

API Reference

Methods

isAvailableAsync()
() => Promise<boolean>
Checks if Sign in with Apple is availableReturns true on iOS 13+ devices, false otherwise
const available = await AppleAuthentication.isAvailableAsync();
if (available) {
  // Show Sign in with Apple button
}
signInAsync(options)
(options: AppleAuthenticationSignInOptions) => Promise<AppleAuthenticationCredential>
Initiates Sign in with Apple flowOpens native authentication dialog
const credential = await AppleAuthentication.signInAsync({
  requestedScopes: [
    AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
    AppleAuthentication.AppleAuthenticationScope.EMAIL,
  ],
});
refreshAsync(options)
(options: AppleAuthenticationRefreshOptions) => Promise<AppleAuthenticationCredential>
Refreshes user credentials
const credential = await AppleAuthentication.refreshAsync({
  user: userId,
});
signOutAsync(options)
(options: AppleAuthenticationSignOutOptions) => Promise<void>
Signs out the user
await AppleAuthentication.signOutAsync({
  user: userId,
});
getCredentialStateAsync(user)
(user: string) => Promise<AppleAuthenticationCredentialState>
Checks credential state for a userReturns one of: AUTHORIZED, REVOKED, NOT_FOUND, TRANSFERRED
const state = await AppleAuthentication.getCredentialStateAsync(userId);
if (state === AppleAuthentication.AppleAuthenticationCredentialState.AUTHORIZED) {
  // User is authenticated
}

Components

AppleAuthenticationButton
component
Native Apple Sign In button componentProps:
  • buttonType: Button type (SIGN_IN, SIGN_UP, CONTINUE)
  • buttonStyle: Visual style (WHITE, WHITE_OUTLINE, BLACK)
  • cornerRadius: Corner radius in pixels
  • onPress: Callback when button is pressed
<AppleAuthentication.AppleAuthenticationButton
  buttonType={AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN}
  buttonStyle={AppleAuthentication.AppleAuthenticationButtonStyle.BLACK}
  cornerRadius={5}
  style={{ width: 200, height: 44 }}
  onPress={handleSignIn}
/>

Types

AppleAuthenticationCredential

user
string
Unique user identifier
email
string | null
User’s email (only on first sign-in)
fullName
AppleAuthenticationFullName | null
User’s full name (only on first sign-in)
realUserStatus
AppleAuthenticationUserDetectionStatus
Indicates if user appears to be real person
identityToken
string | null
JWT identity token
authorizationCode
string | null
Authorization code
state
string | null
State value from request

AppleAuthenticationFullName

givenName
string | null
First name
familyName
string | null
Last name
middleName
string | null
Middle name
namePrefix
string | null
Name prefix (e.g., “Dr.”)
nameSuffix
string | null
Name suffix (e.g., “Jr.”)
nickname
string | null
Nickname

Enums

enum AppleAuthenticationScope {
  FULL_NAME,
  EMAIL,
}

enum AppleAuthenticationButtonType {
  SIGN_IN,
  SIGN_UP,
  CONTINUE,
}

enum AppleAuthenticationButtonStyle {
  WHITE,
  WHITE_OUTLINE,
  BLACK,
}

enum AppleAuthenticationCredentialState {
  AUTHORIZED,
  REVOKED,
  NOT_FOUND,
  TRANSFERRED,
}

Examples

Basic Sign In

import * as AppleAuthentication from 'expo-apple-authentication';

async function signInWithApple() {
  try {
    const credential = await AppleAuthentication.signInAsync({
      requestedScopes: [
        AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
        AppleAuthentication.AppleAuthenticationScope.EMAIL,
      ],
    });

    console.log('User ID:', credential.user);
    
    // Email and name only available on first sign-in
    if (credential.email) {
      console.log('Email:', credential.email);
    }
    
    if (credential.fullName) {
      console.log('Name:', credential.fullName.givenName, credential.fullName.familyName);
    }

    // Identity token for backend verification
    console.log('Identity Token:', credential.identityToken);
    
  } catch (e: any) {
    if (e.code === 'ERR_CANCELED') {
      console.log('User canceled sign in');
    } else {
      console.error('Sign in failed:', e);
    }
  }
}

Check Availability

import * as AppleAuthentication from 'expo-apple-authentication';
import { useState, useEffect } from 'react';

function AppleSignInButton() {
  const [isAvailable, setIsAvailable] = useState(false);

  useEffect(() => {
    checkAvailability();
  }, []);

  async function checkAvailability() {
    const available = await AppleAuthentication.isAvailableAsync();
    setIsAvailable(available);
  }

  if (!isAvailable) {
    return null; // Don't show button on unsupported devices
  }

  return (
    <AppleAuthentication.AppleAuthenticationButton
      buttonType={AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN}
      buttonStyle={AppleAuthentication.AppleAuthenticationButtonStyle.BLACK}
      cornerRadius={5}
      style={{ width: 200, height: 44 }}
      onPress={handleSignIn}
    />
  );
}

Verify Credential State

import * as AppleAuthentication from 'expo-apple-authentication';

async function checkUserStatus(userId: string) {
  const state = await AppleAuthentication.getCredentialStateAsync(userId);

  switch (state) {
    case AppleAuthentication.AppleAuthenticationCredentialState.AUTHORIZED:
      console.log('User is authorized');
      break;
    case AppleAuthentication.AppleAuthenticationCredentialState.REVOKED:
      console.log('User revoked authorization');
      // Sign out user
      break;
    case AppleAuthentication.AppleAuthenticationCredentialState.NOT_FOUND:
      console.log('No credential found');
      break;
  }
}

Complete Auth Example

import * as AppleAuthentication from 'expo-apple-authentication';
import { useState, useEffect } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import * as SecureStore from 'expo-secure-store';

export default function AppleAuthExample() {
  const [user, setUser] = useState<any>(null);
  const [isAvailable, setIsAvailable] = useState(false);

  useEffect(() => {
    checkAppleAuth();
    loadStoredUser();
  }, []);

  async function checkAppleAuth() {
    const available = await AppleAuthentication.isAvailableAsync();
    setIsAvailable(available);
  }

  async function loadStoredUser() {
    const userId = await SecureStore.getItemAsync('appleUserId');
    if (userId) {
      const state = await AppleAuthentication.getCredentialStateAsync(userId);
      if (state === AppleAuthentication.AppleAuthenticationCredentialState.AUTHORIZED) {
        const userData = await SecureStore.getItemAsync('userData');
        if (userData) {
          setUser(JSON.parse(userData));
        }
      }
    }
  }

  async function handleSignIn() {
    try {
      const credential = await AppleAuthentication.signInAsync({
        requestedScopes: [
          AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
          AppleAuthentication.AppleAuthenticationScope.EMAIL,
        ],
      });

      // Store user data
      const userData = {
        userId: credential.user,
        email: credential.email,
        name: credential.fullName?.givenName,
      };

      await SecureStore.setItemAsync('appleUserId', credential.user);
      await SecureStore.setItemAsync('userData', JSON.stringify(userData));
      
      setUser(userData);

      // Send to backend for verification
      await verifyWithBackend(credential.identityToken);
      
    } catch (e: any) {
      if (e.code !== 'ERR_CANCELED') {
        alert('Sign in failed');
      }
    }
  }

  async function handleSignOut() {
    await SecureStore.deleteItemAsync('appleUserId');
    await SecureStore.deleteItemAsync('userData');
    setUser(null);
  }

  async function verifyWithBackend(identityToken: string | null) {
    if (!identityToken) return;
    
    // Send identity token to your backend for verification
    await fetch('https://your-api.com/auth/apple', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ identityToken }),
    });
  }

  if (!isAvailable) {
    return (
      <View style={styles.container}>
        <Text>Sign in with Apple is not available on this device</Text>
      </View>
    );
  }

  if (user) {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>
          Welcome{user.name ? `, ${user.name}` : ''}!
        </Text>
        {user.email && <Text>{user.email}</Text>}
        <Button title="Sign Out" onPress={handleSignOut} />
      </View>
    );
  }

  return (
    <View style={styles.container}>
      <AppleAuthentication.AppleAuthenticationButton
        buttonType={AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN}
        buttonStyle={AppleAuthentication.AppleAuthenticationButtonStyle.BLACK}
        cornerRadius={5}
        style={styles.button}
        onPress={handleSignIn}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    gap: 10,
  },
  button: {
    width: 200,
    height: 44,
  },
  welcome: {
    fontSize: 20,
    fontWeight: 'bold',
  },
});

Platform Support

PlatformSupported
iOS 13+
Android
Web
Sign in with Apple is only available on iOS 13 and later. Always check availability before showing the button.

Configuration

app.json

{
  "expo": {
    "ios": {
      "bundleIdentifier": "com.yourcompany.yourapp",
      "usesAppleSignIn": true
    }
  }
}

Apple Developer Account

  1. Enable “Sign in with Apple” capability in your app identifier
  2. Create a service ID for web authentication (if needed)
  3. Configure domains and redirect URLs

Best Practices

  1. Check Availability: Always check isAvailableAsync() before showing button
  2. First Sign-In: Email and name are only provided on first sign-in, store them
  3. Verify Server-Side: Send identityToken to backend for verification
  4. Credential State: Check credential state on app launch
  5. Handle Revocation: Monitor for REVOKED state and sign user out
User email and full name are only provided during the first sign-in. Store this information as it won’t be provided again.

App Store Requirements

If your app uses third-party sign-in (Google, Facebook, etc.), Apple requires you to also offer Sign in with Apple as an option.

Resources