React Native

SignIn with Apple for React Native

Introduction

In the last tutorial, you built a User login/logout feature to your App using the Parse.User class. Now you will learn how to use Apple Sign-in to retrieve user data from Apple and log in, sign up or link existent users with it. You will also install and configure react-native-apple-authentication lib to achieve that.

The Parse.User.linkWith method is responsible for signing up and logging in users using any third-party authentication method, as long as you pass the right parameters requested by each different provider. After linking the user data to a new or existent Parse.User, Parse will store a valid user session in your device. Future calls to methods like currentAsync will successfully retrieve your User data, just like with regular logins.

At any time, you can access this project via our GitHub repositories to checkout the styles and complete code.

Prerequisites

To complete this tutorial, you will need:

Goal

To build a User LogIn feature using Apple Sign-in on Parse for a React Native App.

Step 1 - Installing dependencies

The most popular way to enable Apple Sign-in on React Native is using react-native-apple-authentication to handle it. Since this library configuration depends on your development environment, target platform, and preferences, set it up following the official docs.

If you are developing for Android, you also need to install the jwt-decode library for decoding Apple JWT tokens.

Note: Make sure to thoroughly follow the instructions for initial setup of Xcode environment, creating your app ID, keys and service ID on Apple Developer portal.

Step 2 - Using Apple Sign-in with Parse

Let’s now create a new method inside the UserLogIn component calling Apple Sign-in authentication modal. The react-native-apple-authentication lib has two separate modules to handle this call based on your user platform, so you need to use appleAuth.performRequest on iOS and appleAuthAndroid.signIn on Android. If the user signs in with Apple, this call will retrieve the user data from Apple and you need to store the id, token, and Apple email for later.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
const doUserLogInApple = async function (): Promise<boolean> {
  try {
    let response = {};
    let appleId = '';
    let appleToken = '';
    let appleEmail = '';
    if (Platform.OS === 'ios') {
      // Performs login request requesting user email
      response = await appleAuth.performRequest({
        requestedOperation: appleAuth.Operation.LOGIN,
        requestedScopes: [appleAuth.Scope.EMAIL],
      });
      // On iOS, user ID and email are easily retrieved from request
      appleId = response.user;
      appleToken = response.identityToken;
      appleEmail = response.email;
    } else if (Platform.OS === 'android') {
      // Configure the request
      appleAuthAndroid.configure({
        // The Service ID you registered with Apple
        clientId: 'YOUR_SERVICE_ID',
        // Return URL added to your Apple dev console
        redirectUri: 'YOUR_REDIRECT_URL',
        responseType: appleAuthAndroid.ResponseType.ALL,
        scope: appleAuthAndroid.Scope.ALL,
      });
      response = await appleAuthAndroid.signIn();
      // Decode user ID and email from token returned from Apple,
      // this is a common workaround for Apple sign-in via web API
      const decodedIdToken = jwt_decode(response.id_token);
      appleId = decodedIdToken.sub;
      appleToken = response.id_token;
      appleEmail = decodedIdToken.email;
    }
    // Format authData to provide correctly for Apple linkWith on Parse
    const authData = {
      id: appleId,
      token: appleToken,
    };
  } catch (error) {
    // Error can be caused by wrong parameters or lack of Internet connection
    Alert.alert('Error!', error);
    return false;
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
const doUserLogInApple = async function (): Promise<boolean> {
  try {
    let response: object = {};
    let appleId: string = '';
    let appleToken: string = '';
    let appleEmail: string = '';
    if (Platform.OS === 'ios') {
      // Performs login request requesting user email
      response = await appleAuth.performRequest({
        requestedOperation: appleAuth.Operation.LOGIN,
        requestedScopes: [appleAuth.Scope.EMAIL],
      });
      // On iOS, user ID and email are easily retrieved from request
      appleId = response.user;
      appleToken = response.identityToken;
      appleEmail = response.email;
    } else if (Platform.OS === 'android') {
      // Configure the request
      appleAuthAndroid.configure({
        // The Service ID you registered with Apple
        clientId: 'YOUR_SERVICE_ID',
        // Return URL added to your Apple dev console
        redirectUri: 'YOUR_SERVICE_URL',
        responseType: appleAuthAndroid.ResponseType.ALL,
        scope: appleAuthAndroid.Scope.ALL,
      });
      response = await appleAuthAndroid.signIn();
      // Decode user ID and email from token returned from Apple,
      // this is a common workaround for Apple sign-in via web API
      const decodedIdToken: object = jwt_decode(response.id_token);
      appleId = decodedIdToken.sub;
      appleToken = response.id_token;
      appleEmail = decodedIdToken.email;
    }
    // Format authData to provide correctly for Apple linkWith on Parse
    const authData: object = {
      id: appleId,
      token: appleToken,
    };
  } catch (error: any) {
    // Error can be caused by wrong parameters or lack of Internet connection
    Alert.alert('Error!', error);
    return false;
  }
};

Note that for Android you need to decode the returning token from Apple because the lib react-native-apple-authentication uses Apple Sign-in web API for authentication. There are restrictions for data access when using this method, so a common workaround for retrieving your user ID and email is through this decoding process, as stated here in the official Parse guides.

After that, you can use Parse.User.linkWith on a new Parse.User object to register a new user and log in. Note that if your user had already signed up using this Apple authentication, linkWith will log him in using the existent account.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
const doUserLogInApple = async function (): Promise<boolean> {
  try {
    let response = {};
    let appleId = '';
    let appleToken = '';
    let appleEmail = '';
    if (Platform.OS === 'ios') {
      // Performs login request requesting user email
      response = await appleAuth.performRequest({
        requestedOperation: appleAuth.Operation.LOGIN,
        requestedScopes: [appleAuth.Scope.EMAIL],
      });
      // On iOS, user ID and email are easily retrieved from request
      appleId = response.user;
      appleToken = response.identityToken;
      appleEmail = response.email;
    } else if (Platform.OS === 'android') {
      // Configure the request
      appleAuthAndroid.configure({
        // The Service ID you registered with Apple
        clientId: 'YOUR_SERVICE_IO',
        // Return URL added to your Apple dev console
        redirectUri: 'YOUR_SERVICE_URL',
        responseType: appleAuthAndroid.ResponseType.ALL,
        scope: appleAuthAndroid.Scope.ALL,
      });
      response = await appleAuthAndroid.signIn();
      // Decode user ID and email from token returned from Apple,
      // this is a common workaround for Apple sign-in via web API
      const decodedIdToken = jwt_decode(response.id_token);
      appleId = decodedIdToken.sub;
      appleToken = response.id_token;
      appleEmail = decodedIdToken.email;
    }
    // Format authData to provide correctly for Apple linkWith on Parse
    const authData = {
      id: appleId,
      token: appleToken,
    };
    // Log in or sign up on Parse using this Apple credentials
    let userToLogin = new Parse.User();
    // Set username and email to match provider email
    userToLogin.set('username', appleEmail);
    userToLogin.set('email', appleEmail);
    return await userToLogin
      .linkWith('apple', {
        authData: authData,
      })
      .then(async (loggedInUser) => {
        // logIn returns the corresponding ParseUser object
        Alert.alert(
          'Success!',
          `User ${loggedInUser.get('username')} has successfully signed in!`,
        );
        // To verify that this is in fact the current user, currentAsync can be used
        const currentUser = await Parse.User.currentAsync();
        console.log(loggedInUser === currentUser);
        // Navigation.navigate takes the user to the screen named after the one
        // passed as parameter
        navigation.navigate('Home');
        return true;
      })
      .catch(async (error) => {
        // Error can be caused by wrong parameters or lack of Internet connection
        Alert.alert('Error!', error.message);
        return false;
      });
  } catch (error) {
    // Error can be caused by wrong parameters or lack of Internet connection
    Alert.alert('Error!', error);
    return false;
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
const doUserLogInApple = async function (): Promise<boolean> {
  try {
    let response: object = {};
    let appleId: string = '';
    let appleToken: string = '';
    let appleEmail: string = '';
    if (Platform.OS === 'ios') {
      // Performs login request requesting user email
      response = await appleAuth.performRequest({
        requestedOperation: appleAuth.Operation.LOGIN,
        requestedScopes: [appleAuth.Scope.EMAIL],
      });
      // On iOS, user ID and email are easily retrieved from request
      appleId = response.user;
      appleToken = response.identityToken;
      appleEmail = response.email;
    } else if (Platform.OS === 'android') {
      // Configure the request
      appleAuthAndroid.configure({
        // The Service ID you registered with Apple
        clientId: 'com.back4app.userguide',
        // Return URL added to your Apple dev console
        redirectUri: 'https://tuhl.software/back4appuserguide/',
        responseType: appleAuthAndroid.ResponseType.ALL,
        scope: appleAuthAndroid.Scope.ALL,
      });
      response = await appleAuthAndroid.signIn();
      // Decode user ID and email from token returned from Apple,
      // this is a common workaround for Apple sign-in via web API
      const decodedIdToken: object = jwt_decode(response.id_token);
      appleId = decodedIdToken.sub;
      appleToken = response.id_token;
      appleEmail = decodedIdToken.email;
    }
    // Format authData to provide correctly for Apple linkWith on Parse
    const authData: object = {
      id: appleId,
      token: appleToken,
    };
    // Log in or sign up on Parse using this Apple credentials
    let userToLogin: Parse.User = new Parse.User();
    // Set username and email to match provider email
    userToLogin.set('username', appleEmail);
    userToLogin.set('email', appleEmail);
    return await userToLogin
      .linkWith('apple', {
        authData: authData,
      })
      .then(async (loggedInUser: Parse.User) => {
        // logIn returns the corresponding ParseUser object
        Alert.alert(
          'Success!',
          `User ${loggedInUser.get('username')} has successfully signed in!`,
        );
        // To verify that this is in fact the current user, currentAsync can be used
        const currentUser: Parse.User = await Parse.User.currentAsync();
        console.log(loggedInUser === currentUser);
        // Navigation.navigate takes the user to the screen named after the one
        // passed as parameter
        navigation.navigate('Home');
        return true;
      })
      .catch(async (error: object) => {
        // Error can be caused by wrong parameters or lack of Internet connection
        Alert.alert('Error!', error.message);
        return false;
      });
  } catch (error: any) {
    // Error can be caused by wrong parameters or lack of Internet connection
    Alert.alert('Error!', error);
    return false;
  }
};

Add this function to your UserSignIn component and assign it to your Apple button onPress parameter. Go ahead and test your new function. Note that the user will be redirected to your home screen after successfully registering and/or signing in.

React Native Back4App

Step 3 - Verifying user sign in and session creation

To make sure that the Apple sign-in worked, you can look at your Parse dashboard and see your new User (if your Apple authentication data didn’t belong to another user), containing the Apple authData parameters.

React Native Back4App

You can also verify that a valid session was created in the dashboard, containing a pointer to that User object.

React Native Back4App

Step 4 - Linking an existing User to Apple Sign-in

Another linkWith possible use is to link an existing user with another auth provider, in this case, Apple. Add this function that calls linkWith the same way you did in UserLogIn to your HelloUser component or directly to your home screen. The only difference here is that instead of calling the method from an empty Parse.User, you will use it from the logged-in user object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
const doUserLinkApple = async function (){
  try {
    let response = {};
    let appleId = '';
    let appleToken = '';
    if (Platform.OS === 'ios') {
      // Performs login request requesting user email
      response = await appleAuth.performRequest({
        requestedOperation: appleAuth.Operation.LOGIN,
        requestedScopes: [appleAuth.Scope.EMAIL],
      });
      // On iOS, user ID and email are easily retrieved from request
      appleId = response.user;
      appleToken = response.identityToken;
    } else if (Platform.OS === 'android') {
      // Configure the request
      appleAuthAndroid.configure({
        // The Service ID you registered with Apple
        clientId: 'YOUR_SERVICE_ID',
        // Return URL added to your Apple dev console
        redirectUri: 'YOUR_REDIRECT_URL',
        responseType: appleAuthAndroid.ResponseType.ALL,
        scope: appleAuthAndroid.Scope.ALL,
      });
      response = await appleAuthAndroid.signIn();
      // Decode user ID and email from token returned from Apple,
      // this is a common workaround for Apple sign-in via web API
      const decodedIdToken = jwt_decode(response.id_token);
      appleId = decodedIdToken.sub;
      appleToken = response.id_token;
    }
    // Format authData to provide correctly for Apple linkWith on Parse
    const authData = {
      id: appleId,
      token: appleToken,
    };
    let currentUser = await Parse.User.currentAsync();
    // Link user with his Apple Credentials
    return await currentUser
      .linkWith('apple', {
        authData: authData,
      })
      .then(async (loggedInUser) => {
        // logIn returns the corresponding ParseUser object
        Alert.alert(
          'Success!',
          `User ${loggedInUser.get(
            'username',
          )} has successfully linked his Apple account!`,
        );
        // To verify that this is in fact the current user, currentAsync can be used
        currentUser = await Parse.User.currentAsync();
        console.log(loggedInUser === currentUser);
        return true;
      })
      .catch(async (error) => {
        // Error can be caused by wrong parameters or lack of Internet connection
        Alert.alert('Error!', error.message);
        return false;
      });
  } catch (error) {
    // Error can be caused by wrong parameters or lack of Internet connection
    Alert.alert('Error!', error);
    return false;
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
const doUserLinkApple = async function (): Promise<boolean> {
  try {
    let response: object = {};
    let appleId: string = '';
    let appleToken: string = '';
    if (Platform.OS === 'ios') {
      // Performs login request requesting user email
      response = await appleAuth.performRequest({
        requestedOperation: appleAuth.Operation.LOGIN,
        requestedScopes: [appleAuth.Scope.EMAIL],
      });
      // On iOS, user ID and email are easily retrieved from request
      appleId = response.user;
      appleToken = response.identityToken;
    } else if (Platform.OS === 'android') {
      // Configure the request
      appleAuthAndroid.configure({
        // The Service ID you registered with Apple
        clientId: 'YOUR_SERVICE_ID',
        // Return URL added to your Apple dev console
        redirectUri: 'YOUR_SERVICE_URL',
        responseType: appleAuthAndroid.ResponseType.ALL,
        scope: appleAuthAndroid.Scope.ALL,
      });
      response = await appleAuthAndroid.signIn();
      // Decode user ID and email from token returned from Apple,
      // this is a common workaround for Apple sign-in via web API
      const decodedIdToken: object = jwt_decode(response.id_token);
      appleId = decodedIdToken.sub;
      appleToken = response.id_token;
    }
    // Format authData to provide correctly for Apple linkWith on Parse
    const authData: object = {
      id: appleId,
      token: appleToken,
    };
    let currentUser: Parse.User = await Parse.User.currentAsync();
    // Link user with his Apple Credentials
    return await currentUser
      .linkWith('apple', {
        authData: authData,
      })
      .then(async (loggedInUser: Parse.User) => {
        // logIn returns the corresponding ParseUser object
        Alert.alert(
          'Success!',
          `User ${loggedInUser.get(
            'username',
          )} has successfully linked his Apple account!`,
        );
        // To verify that this is in fact the current user, currentAsync can be used
        currentUser = await Parse.User.currentAsync();
        console.log(loggedInUser === currentUser);
        return true;
      })
      .catch(async (error: object) => {
        // Error can be caused by wrong parameters or lack of Internet connection
        Alert.alert('Error!', error.message);
        return false;
      });
  } catch (error: any) {
    // Error can be caused by wrong parameters or lack of Internet connection
    Alert.alert('Error!', error);
    return false;
  }
};

Assign this function to a Apple button onPress parameter on your home screen. Test your new function, noting that the Parse.User object authData value will be updated with the new auth provider data. Verify if the user has indeed updated in your Parse server dashboard.

React Native Back4App

Conclusion

At the end of this guide, you learned how to log in, sign up or link existing Parse users on React Native using Apple Sign-in with react-native-apple-authentication. In the next guide, we will show you how to perform useful user queries.