Mobile Application Authentication in React Native Mobile App — Part 6

Photo by Matthew Fournier on Unsplash

In this section we are going add authentication to our application. We are going to create sign-in screen. When the user signs in, we’ll store a token on the device, and allowing them to bypass the sign-in screen. We’ll also add where a user can click a button to log out of the application and remove the token from their device.

This article is part of a multi-part series. For other parts, see

  1. Tabbed Routing with React Navigation in React Native Mobile App — Part 2
  2. Stack Navigation in React Native Mobile App — Part 3
  3. Creating List & Scrollable Content Views in React Native Mobile App — Part 4
  4. Call GraphQL API in React Native Mobile App — Part 5

What is SecureStore?

expo-secure-store provides a way to encrypt and securely store key–value pairs locally on the device. Each Expo project has a separate storage system and has no access to the storage of other Expo projects. Please note that for iOS standalone apps, data stored with expo-secure-store can persist across app installs.

Install the SecoreStore module

expo install expo-secure-store

Create sign-in screen

authloading.js: This will be the screen, we will use the screen to check if a token is present or not and navigate the user to either the sign-in screen or the application content.

signin.js: This is the screen where a user can sign in to their account. After a successful login attempt, we will store a token on the device.

settings.js: In the settings screen, a user will be able to click a button and log out of the application. Once they are logged out, they will be routed back to the sign-in screen.

Create a src/screens/signin.js screen

import React from 'react';
import { View, Button, Text } from 'react-native';
const SignIn = props => {
return (
<View>
<Button title="Sign in!" />
</View>
);
}
SignIn.navigationOptions = {
title: 'Sign In'
};
export default SignIn;

Import Expo SecureStore library in src/screens/signin.js

import * as SecureStore from 'expo-secure-store';

Create a component called Loading.js in src/components folder

import React from 'react';
import { View, ActivityIndicator } from 'react-native';
import styled from 'styled-components/native';
const LoadingWrap = styled.View`
flex: 1;
justify-content: center;
align-items: center;
`;
const Loading = () => {
return (
<LoadingWrap>
<ActivityIndicator size="large" />
</LoadingWrap>
);
};
export default Loading;

Create a component src/screens/authloading.js

import React, { useEffect } from 'react';
import * as SecureStore from 'expo-secure-store';
import Loading from '../components/Loading';const AuthLoading = props => {
return <Loading />;
};
export default AuthLoading;

Create a component src/screens/settings.js

import React from 'react';
import { View, Button } from 'react-native';
import * as SecureStore from 'expo-secure-store';
const Settings = props => {
return (
<View>
<Button title="Sign Out" />
</View>
);
};
Settings.navigationOptions = {
title: 'Settings'
};
export default Settings;

Update src/screens/index.js

import AuthLoading from './authloading';
import SignIn from './signin';
import Settings from './settings';

Create a new StackNavigator for authentication and settings screens

const AuthStack = createStackNavigator({
SignIn: SignIn
});
const SettingsStack = createStackNavigator({
Settings: Settings
});

Create createSwitchNavigator

Import createSwitchNavigator in src/screens/index.js

import { createAppContainer, createSwitchNavigator } from 'react-navigation';

Create SwitchNavigator

const SwitchNavigator = createSwitchNavigator(
{
AuthLoading: AuthLoading,
Auth: AuthStack,
App: TabNavigator
},
{
initialRouteName: 'AuthLoading'
}
);

Change existing export statement with the SwitchNavigator

export default createAppContainer(SwitchNavigator);

All together src/screens/index.js

import React from 'react';
import { createAppContainer, createSwitchNavigator } from 'react-navigation';
import { createBottomTabNavigator } from 'react-navigation-tabs';
import { createStackNavigator } from 'react-navigation-stack';
import { MaterialCommunityIcons, MaterialIcons } from '@expo/vector-icons';
import AuthLoading from './authloading';
import SignIn from './signin';
import Settings from './settings';
import Feed from './feed';
import Favorites from './favorites';
import MyBooks from './mybooks';
import BookScreen from './book';
//stacked navivator
const FeedStack = createStackNavigator({
Feed: Feed,
Book: BookScreen
});
const MyBooksStack = createStackNavigator({
MyBooks: MyBooks,
Book: BookScreen
});
const FavoritesStack = createStackNavigator({
Favorites: Favorites,
Book: BookScreen
});

const AuthStack = createStackNavigator({
SignIn: SignIn
});
const SettingsStack = createStackNavigator({
Settings: Settings
});
const TabNavigator = createBottomTabNavigator({
FeedScreen: {
screen: FeedStack,
navigationOptions: {
tabBarLabel: 'Feed',
tabBarIcon: () => (
<MaterialCommunityIcons name="home" size={24} color={'black'} />
)
}
},
MyBooksScreen: {
screen: MyBooksStack,
navigationOptions: {
tabBarLabel: 'My Books',
tabBarIcon: () => (
<MaterialIcons name="library-books" size={24} color={'black'} />
)
}
},
FavoriteScreen: {
screen: FavoritesStack,
navigationOptions: {
tabBarLabel: 'Favorites',
tabBarIcon: () => (
<MaterialCommunityIcons name="star" size={24} color={'black'} />
)
}
},
Settings: {
screen: SettingsStack,
navigationOptions: {
tabBarLabel: 'Settings',
tabBarIcon: () => (
<MaterialIcons name="settings" size={24} color = {'black'} />
)
}
}

});
const SwitchNavigator = createSwitchNavigator(
{
AuthLoading: AuthLoading,
Auth: AuthStack,
App: TabNavigator
},
{
initialRouteName: 'AuthLoading'
}
);
export default createAppContainer(SwitchNavigator);

Update src/screens/authloading.js

const AuthLoading = props => {
const checkLoginState = async () => {

// retrieve the value of the token
const userToken = await SecureStore.getItemAsync('token');

// navigate to the app screen if a token is present
// else navigate to the auth screen
props.navigation.navigate(userToken ? 'App' : 'Auth');
}
// call checkLoginState when the component mounts
useEffect(() => {
checkLoginState();
});
return <Loading />;
};

Update src/screens/signin.js

import React from 'react';
import { View, Button, Text } from 'react-native';
import * as SecureStore from 'expo-secure-store';
const SignIn = props => {

// store the token then navigate to the app's main screen
const storeToken = () => {
SecureStore.setItemAsync('token', 'abc').then(
props.navigation.navigate('App')
);
};
return (
<View>
<Button title="Sign in!" onPress={storeToken} />
</View>
);
}
SignIn.navigationOptions = {
title: 'Sign In'
};
export default SignIn;

Run the application

npm start

We can some error is giving. Error: Unable to resolve module punycode from F:\javascript-projects\react-native-projects\native-book-app\node_modules\markdown-it\lib\index.js: punycode could not be found within the project.

Let us install punycode module

npm install punycode --save

Run again application

npm start

This time application is running without any error. And you should get scrren like below:

When user is click on Sign in button, a token is stored via SecureStore. After that it will redirected to the screen screen.

Add sign out functionality

import React from 'react';
import { View, Button } from 'react-native';
import * as SecureStore from 'expo-secure-store';
const Settings = props => {//remove the token and navigate to the Auth screen
const signOut = () => {
SecureStore.deleteItemAsync('token').then(
props.navigation.navigate('Auth')
);
};
return (
<View>
<Button title="Sign Out" onPress={signOut} />
</View>
);
};
Settings.navigationOptions = {
title: 'Settings'
};
export default Settings;

Click the settings icon and you should get the screen like below:

Create User Form

import React, { useState } from 'react';
import { View, Text, TextInput, Button } from 'react-native';
const UserForm = props => {
const [email, setEmail] = useState();
const [password, setPassword] = useState();
return (
<View>
<Text>Email</Text>
<TextInput
onChangeText={text => setEmail(text)}
value={email}
textContentType="emailAddress"
autoCompleteType="email"
autoFocus={true}
autoCapitalize="none"
/>
<Text>Password</Text>
<TextInput
onChangeText={text => setPassword(text)}
value={password}
textContentType="password"
secureTextEntry={true}
/>
<Button title="Log In" />
</View>
);
}
export default UserForm;

Update sign-in screen

import React from 'react';
import { View, Button, Text } from 'react-native';
import * as SecureStore from 'expo-secure-store';
import UserForm from '../components/UserForm';
const SignIn = props => {

// store the token then navigate to the app's main screen
const storeToken = () => {
SecureStore.setItemAsync('token', 'abc').then(
props.navigation.navigate('App')
);
};
return (
<View>
<UserForm />
</View>
);
}
SignIn.navigationOptions = {
title: 'Sign In'
};
export default SignIn;

Add some styling to the UserForm.js

import React, { useState } from 'react';
import { View, Text, TextInput, Button, TouchableOpacity } from 'react-native';
import styled from 'styled-components/native';
const Form = styled.View`
padding: 10px;
`;
const StyledInput = styled.TextInput`
border: 1px solid gray;
font-size: 18px;
padding: 8px;
margin-bottom: 25px;
`;
const FormLabel = styled.Text`
font-size: 15px;
font-weight: bold;
`;
const FormBtn = styled.TouchableOpacity`
background: #0077cc;
width: 100%;
padding: 8px;
`;
const BtnText = styled.Text`
text-align: center;
color: #fff;
font-weight: bold;
font-size: 18px;
`;
const UserForm = props => {
const [email, setEmail] = useState();
const [password, setPassword] = useState();
return (
<Form>
<FormLabel>Email</FormLabel>
<StyledInput
onChangeText={text => setEmail(text)}
value={email}
textContentType="emailAddress"
autoCompleteType="email"
autoFocus={true}
autoCapitalize="none"
/>
<FormLabel>Password</FormLabel>
<StyledInput
onChangeText={text => setPassword(text)}
value={password}
textContentType="password"
secureTextEntry={true}
/>
<FormBtn>
<BtnText>Submit</BtnText>
</FormBtn>
</Form>
);
}
export default UserForm;

Restart application if it is not running

npm start

You should get scrren like below:

Authenticate with GraphQL Mutations

Write GraphQL mutation in src/screens/signin.js

import { useMutation, gql } from '@apollo/client';const SIGNIN_USER = gql`
mutation signIn($email: String, $password: String!) {
signIn(email: $email, password: $password)
}
`;

Update the Token function to store a token string passed as a parameter

// store the token then navigate to the app's main screen
const storeToken = token => {
SecureStore.setItemAsync('token', token).then(
props.navigation.navigate('App')
);
};

Update the sign in component as a GraphQL mutation

const [signIn, { loading, error }] = useMutation(SIGNIN_USER, {
onCompleted: data => {
storeToken(data.signIn)
}
});


if (loading) return <Loading />;
return (
<React.Fragment>
{error && <Text>Error signing in!</Text>}

<UserForm
action={signIn}
formType="signIn"
navigation={props.navigation}

/>
</React.Fragment>
);

Full code of src/screens/signin.js

import React from 'react';
import { View, Button, Text } from 'react-native';
import * as SecureStore from 'expo-secure-store';
import UserForm from '../components/UserForm';
import { useMutation, gql } from '@apollo/client';
const SIGNIN_USER = gql`
mutation signIn($email: String, $password: String!) {
signIn(email: $email, password: $password)
}
`;
const SignIn = props => {

// store the token then navigate to the app's main screen
const storeToken = token => {
SecureStore.setItemAsync('token', token).then(
props.navigation.navigate('App')
);
};
const [signIn, { loading, error }] = useMutation(SIGNIN_USER, {
onCompleted: data => {
storeToken(data.signIn)
}
});
if (loading) return <Loading />;return (
<React.Fragment>
{error && <Text>Error signing in!</Text>}
<UserForm
action={signIn}
formType="signIn"
navigation={props.navigation}
/>
</React.Fragment>
);
}
SignIn.navigationOptions = {
title: 'Sign In'
};
export default SignIn;

Add submit event in src/components/UserForm.js

const handleSubmit = () => {
props.action({
variables: {
email: email,
password: password
}
});
};
<FormBtn onPress={handleSubmit}>
<BtnText>Submit</BtnText>
</FormBtn>

Authenticated GraphQL Queries

Install apollo-link-content

Apollo Link is a simple yet powerful way to describe how you want to get the result of a GraphQL operation, and what you want to do with the results.

npm install apollo-link-context --save

Imports and update the Apollo Client dependencies to include createHttpLink and setContext in src/Main.js

import React from 'react';
import { ApolloClient, ApolloProvider, InMemoryCache, createHttpLink } from '@apollo/client';
import { setContext } from 'apollo-link-context';
import * as SecureStore from 'expo-secure-store';
import Screens from './screens';
import getEnvVars from '../config';
const { API_URL } = getEnvVars();
const uri = API_URL;
const cache = new InMemoryCache();
const httpLink = createHttpLink({ uri });
// return the headers to the context
const authLink = setContext(async (_, { headers }) => {
return {
headers: {
...headers,
authorization: (await SecureStore.getItemAsync('token')) || ''
}
};
});
// Create a instance of Apollo Client
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache
});
const Main = () => {
return (
<ApolloProvider client={client}>
<Screens />
</ApolloProvider>
);
};
export default Main;

Run the application

npm start

We can see react native application in running on both device Android and iPhone. You should get screen on your mobile like below:

We are calling GraphQL api in this series. We have users in our GraphQL api. We are using that user for signin. Enter email and password, then click on Submit. After successfull login, You should get screen like below:

Click on Settings, you can sign out.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store