React Native

React Native authentication using Relay

Introduction

Using the GraphQL API, after signing up or logging a user in, you will receive a session token that you can use to retrieve the logged user at any time. The session token comes from a Relay Mutation. You will find those Relay Mutation examples on the previous guides of Sign Up or Log In.

The session token value represents the current session and controls if the user is authenticated or not. At the moment of authentication, this value needs to start to be on header params. On Relay, we use the Environment to handle the header params, so you should insert the session token inside this file.

After adding the session to the headers, each request will be authenticated by Back4App and, the user will access the private resources.

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

Goal

Authenticate the user requests on Back4App using the session token on header params.

Prerequisites

  • An app created at Back4App using the Parse Server Version 3.10 or above.
  • You have to conclude the Relay Environment setup tutorial;
  • You have to conclude the React Native Login sample using Relay;
  • For this tutorial, we are going to use the Expo as a React Native framework;
  • For this tutorial, we are going to use Javascript as our default implementation language;
  • For this tutorial, we are going to use Async Storage;

Step 1 - Install Async Storage

After conclude the tutorials Sign Up or Log In, your app will receive a session token. Let’s store the token using the Async Storage. Follow the official docs to install the Async Storage Lib on your App.

You can use async storage, redux, or your preferred local storage solution. You only make sure that this value will be available in the Environment.

Step 2 - Retrieve the token

Let’s go ahead using the last guide code. You’ll need to get the session token and persist this value in your application using Async Storage.

Start by changing the session token state management from useState hook to Async Storage. The first step is to create a function inside of the environment file to retrieve the session token from Async Storage.

Import the Async Storage:

1
import AsyncStorage from '@react-native-async-storage/async-storage';

Now, create the function:

1
2
3
4
 export const getSessionToken = async () => {
   const sessionToken = await AsyncStorage.getItem('sessionToken');
   return sessionToken;
 };

Step 3 - Save the token on the Client-side

Let’s now improve the Sign In component to persist the session token instead of managing it using the useState hook. The component will now keep the logged-in state even when reloading the App because it has the session token persisted.

Open the Sign In component. Inside of the onCompleted from onSubmit, saving the session token on Async Storage getting the following result:

Then improve the onCompleted:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
onCompleted: async (response) => {
    if(!response?.logIn || response?.logIn === null) {
      alert('Error while logging');
      return;
    }

    const { viewer } = response?.logIn;
    const { sessionToken, user } = viewer;

    if (sessionToken !== null) {
      setUserLogged(user);
      alert(`user ${user.username} successfully logged`);
      await AsyncStorage.setItem('sessionToken', sessionToken);
      return;
    }
},

After, inside of the SignIn component declaration, create a new useState for the session token:

1
const [sessionToken, setSessionToken] = useState(null);

Add a useEffect to be called every time that the component is mounted and check if has a session token (import the getSessionToken from environment file:

1
2
3
4
5
6
useEffect(() => {
  (async () => {
    const sT = await getSessionToken();
    setSessionToken(sT);
  })();
}, []);

By last, let’s change again the onCompleted for now handle the new useState, getting the new lines of code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
onCompleted: async (response) => {
    if (!response?.logIn || response?.logIn === null) {
    alert('Error while logging');
    return;
    }
    
    const { viewer } = response?.logIn;
    const { sessionToken, user } = viewer;
    
    if (sessionToken !== null) {
       setSessionToken(sessionToken);
       await AsyncStorage.setItem('sessionToken', sessionToken);
       return;
  
    }
},

Remove the useState for user logged, the both lines below from respective places:

1
const [userLogged, setUserLogged] = useState(null);

and

1
setUserLogged(user);

We avoid the alert and start to set the info of the user and the token in a useState followed by the Async Storage saving the token.

Changes the if to handle now the session token.

1
2
3
4
5
6
7
if (sessionToken) {
  return (
    <View>
      <Text>User logged</Text>
    </View>
  );
}

Step 4 - Final result of SignIn component

After all changes, the SignIn component will be similar to the below.

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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import React, {useEffect, useState} from 'react';
import LogInMutation from './mutations/LogInMutation';
import environment, { getSessionToken } from '../../relay/environment';
import {FormikProvider, useFormik} from 'formik';
import { Button, Text, TextInput, View, TouchableOpacity } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';

const SignIn = () => {
  const [sessionToken, setSessionToken] = useState(null);

  useEffect(() => {
    (async () => {
      const sT = await getSessionToken();
      setSessionToken(sT);
    })();
  }, []);

  const onSubmit = async (values) = {
    const { username, password } = values;
    const input = {
      username,
      password,
    };

    LogInMutation.commit({
      environment,
      input,
      onCompleted: async (response) => {
        if (!response?.logIn || response?.logIn === null) {
          alert('Error while logging');
          return;
        }

        const { viewer } = response?.logIn;
        const { sessionToken, user } = viewer;

        if (sessionToken !== null) {
          setSessionToken(sessionToken);
          setUserLogged(user);

          await AsyncStorage.setItem('sessionToken', sessionToken);
          return;
        }
      },
      onError: (errors) => {
        alert(errors[0].message);
      },
    });
  };

  const formikbag = useFormik({
    initialValues: {
      username: '',
      password: '',
    },
    onSubmit,
  });

 const { handleSubmit, setFieldValue } = formikbag;

  if (sessionToken) {
    return (
      <View style={ {marginTop: 15, alignItems: 'center'} }>
        <Text>User logged</Text>
      </View>
    );
  }

  return (
      <FormikProvider value={formikbag}>
          <View style={Styles.login_wrapper}>
              <View style={Styles.form}>
                  <Text>Username</Text>
                  <TextInput
                      name={"username"}
                      style={Styles.form_input}
                      autoCapitalize="none"
                      onChangeText={(text) => setFieldValue("username", text)}
                  />
                  <Text>Password</Text>
                  <TextInput
                      style={Styles.form_input}
                      name={"password"}
                      autoCapitalize="none"
                      secureTextEntry
                      onChangeText={(text) => setFieldValue("password", text)}
                  />
                  <TouchableOpacity onPress={() => handleSubmit()}>
                      <View style={Styles.button}>
                          <Text style={Styles.button_label}>{"Sign in"}</Text>
                      </View>
                  </TouchableOpacity>
              </View>
          </View>
      </FormikProvider>
  );
};

export default SignIn;

Step 5 - Testing

It’s time to test the new changes of Sign In component. To make sure there is no one user logged in, kill your application and open again.

Remember also to clear the Async Storage. You can do it calling the AsyncStorage.clear() method and clear the actual state.

Log in again and you will see the following message.

Step 6 - Set the Session token on Relay Environment

Now, let’s insert the session token on the application’s requests to Back4App GraphQL API. Inside of the Environment file, retrieve the sessionToken and add it to the headers object. You should pass the sessionToken on the variable X-Parse-Session-Token on the headers.
Here, we will reuse the getSessionToken function we already created.

Create a function before the fetchQuery function and paste the following code:

1
2
3
4
5
6
7
8
9
10
11
export const getToken = async () => {
  const sessionToken = await getSessionToken();

  if (sessionToken) {
    return {
      'X-Parse-Session-Token': sessionToken,
    };
  }

  return {};
};

The function above retrieve the session token only if it exists. Now, add it to the headers object deconstructing it.

1
2
3
4
5
6
7
const headers = {
  Accept: 'application/json',
  'Content-type': 'application/json',
  'X-Parse-Application-Id': 'YOUR_APP_ID_HERE',
  'X-Parse-Client-Key': 'YOUR_CLIENT_KEY_HERE',
  ...await getToken(),
};

Right below of headers, there is the try catch for make the request. Let’s set an if after the request that will handle when the http status of the request will be 401. This will mean that the actually token is not valid anymore. So, we will clear the storage and kill the current user:

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
try {
    const response = await fetch(`https://parseapi.back4app.com/graphql`, {
        method: "POST",
        headers,
        body,
    });

    const data = await response.json();

    
    // this if will retrive the response when status code 401 and clear the session token
    if (response.status === 401) {
        await AsyncStorage.getItem("sessionToken");
        return;
    }

    if (data.errors) {
        throw data.errors;
    }

    return data;
} catch (err) {
    console.log("err on fetch graphql", err);

    throw err;
}

Now, your application can start to retrieve private resources from the Back4App backend. And, if the session token does not exist, won’t break because we won’t pass it.

Do not forget to configure the security mechanisms to guarantee the desired level of access for users. To better understand access the link security docs from Parse.

Conclusion

In this guide, you have saved the session token using async storage and, now can start to retrieve resources that need a user logged.

In the next doc, let’s prepare a component that will retrieve the info about the user logged and stop using a useState to show it.

For user SignUp you can follow the same approach of this tutorial to handle the session token