React Native

User Session management using Relay

Introduction

Now that you have logged the user in, it is time to learn how to manage user sessions. Sessions represent an instance of a user logged into a device. In Parse, the Sessions are automatically created when users log in or sign up. They are automatically deleted when users log out.

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 Sign 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, the Back4App will authenticate each request and, the user will access the private resources.

With the user has been logged, it is time to handle the session. With Back4App, the session of the user is a session token.

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 Sign Up or Sign In, you will receive a session token. Let’s store the token using the Async Storage. Follow the official docs to do the installation step. This Back4App application is using a React Native project with a version >= 0.63 and IOS. So, we install it using yarn and after install the pods.
Let’s use the last doc, User Login. From the result of Log In Mutation, get the session token and persist this value in your application.

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 - Saving the token on the Client-side

Using the last tutorial, let’s improve the Sign In component to persist the session token instead of saving the user info inside a state. The component will now maintain the logged-in state even when reloading the App because it has the session token persisted.

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

Start importing the Async Storage:

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

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;
    }
},

Step 3 - Retrieve the token

Let’s change from logged info to start using the session token on useState.
Create a function outside of the Sign In component declaration. This function will be responsible for getting the session token from Async Storage. Copy and paste the code below.

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

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:

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
17
18
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;
    
    }
},

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 final result of SignIn component should look;

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
100
101
102
103
104
105
import React, {useEffect, useState} from 'react';
import LogInMutation from './mutations/LogInMutation';
import {Environment} from '../../relay';
import {FormikProvider, useFormik} from 'formik';
import {Button, Text, View} from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';

import styled from 'styled-components';

const TextInput = styled.TextInput``;

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

const SignIn = () => {
  const [userLogged, setUserLogged] = useState({});
  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: 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={ {marginTop: 15, alignItems: 'center'} }>
        <Text>Username</Text>
        <TextInput
          name={'username'}
          style={ {width: 150, height: 30, borderColor: 'gray', borderWidth: 1} }
          autoCapitalize="none"
          onChangeText={(text) => setFieldValue('username', text)}
        />

        <Text style={ {marginTop: 15} }>Password</Text>
        <TextInput
          style={ {width: 150, height: 30, borderColor: 'gray', borderWidth: 1} }
          name={'password'}
          autoCapitalize="none"
          secureTextEntry={true}
          onChangeText={(text) => setFieldValue('password', text)}
        />
        <Button title={'sign in'} onPress={() => handleSubmit()} />
      </View>
    </FormikProvider>
  );
};

export default SignIn;

Step 5 - Testing

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

Between the test if the login persist, remember to clear the Async Storage
You can do it calling the AsyncStorage.clear() method and clear the actual state.

Do the login with an existent user. After, the result should be:

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 handles the session token only when exists. Otherwise, an empty object returns.

Now, add it to the headers object deconstructing it.

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

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