We use cookies and other tracking technologies to improve your browsing experience on our site, analyze site traffic, and understand where our audience is coming from. To find out more, please read our privacy policy.

By choosing 'I Accept', you consent to our use of cookies and other tracking technologies.

We use cookies and other tracking technologies to improve your browsing experience on our site, analyze site traffic, and understand where our audience is coming from. To find out more, please read our privacy policy.

By choosing 'I Accept', you consent to our use of cookies and other tracking technologies. Less

We use cookies and other tracking technologies... More

Login or register
to apply for this job!

Login or register to start contributing with an article!

Login or register
to see more jobs from this company!

Login or register
to boost this post!

Show some love to the author of this blog by giving their post some rocket fuel 🚀.

Login or register to search for your ideal job!

Login or register to start working on this issue!

Engineers who find a new job through JavaScript Works average a 15% increase in salary 🚀

Blog hero image

Persisting User authentication in a React-Native App

David Abimbola 11 September, 2018 | 6 min read

blog1.png

It is very necessary to provide ways to authenticate your users in your application. Finding working examples online has been tough so I thought writing about it will be a good idea. This article I will say is an extended tutorial to the one here. So I suggest you take a look at it first before delving into this.

The Idea

The idea was gotten here react-navigation but this has some downside. After a User successfully login a token is returned from the API-server and this token is saved in AsyncStorage.

AsyncStorage is a simple, unencrypted, asynchronous, persistent, key-value storage system that is global to the app. It should be used instead of LocalStorage.

For more info on AsyncStorage visit react-native.

The token is a credential used to access protected content from the API-server. Giving a scenario where the token needs to be added to the headers before making an API request. Yes, the token is saved in AsyncStorage but we would need to get the token from AsyncStorage first then attach it to the headers before making the request.

Giving a Scenario where we need to make 50 AJAX request in our application and each time we do this we get the Token from AsyncStorage.Getting the token from a AsyncStorage is a synchronous operation and it takes time before the token is gotten. This can be be quite annoying and can reduce our app performance. Why can’t we just make the synchronous request once instead of making it 50 times and save it somewhere, then we get it from there anytime we need it.

My Solution

Saving the token in Redux or using the new React Context-API sounds like a good idea.Wait!!! what is Redux ?

*Redux can be said to be a predictable state container for JavaScript apps. If you are new to Redux I will suggest you visit redux for more Information. Context provides a way to pass data through the component tree without having to pass props down manually at every level. You can find more information about React Context-API here react.*

Kent C. Dodds also has a good article about it, visit here for more info.

Wes Bos also has a good tutorial, visit here for more info.

Code

We will be using the code in react-navigation website as a boiler plate. It can be found here.

Lets first create our Redux solution. we will definitely need to install redux, react-redux, and redux-thunk into our boiler plate code as shared above.

npm i redux react-redux redux-thunk

I assume we already know how redux providers and redux store works so i will just go ahead with actions.js and reducers.js

Here is our actions.js:

import { AsyncStorage } from 'react-native';

export const getToken = (token) => ({
    type: 'GET_TOKEN',
    token,
});

export const saveToken = token => ({
    type: 'SAVE_TOKEN',
    token
});

export const removeToken = () => ({
    type: 'REMOVE_TOKEN',
});

export const loading = bool => ({
    type: 'LOADING',
    isLoading: bool,
});

export const error = error => ({
    type: 'ERROR',
    error,
});



export const getUserToken = () => dispatch => 

 AsyncStorage.getItem('userToken')
        .then((data) => {
            dispatch(loading(false));
            dispatch(getToken(data));
        })
        .catch((err) => {
            dispatch(loading(false));
            dispatch(error(err.message || 'ERROR'));
        })



export const saveUserToken = (data) => dispatch =>
    AsyncStorage.setItem('userToken', 'abc')
        .then((data) => {
            dispatch(loading(false));
            dispatch(saveToken('token saved'));
        })
        .catch((err) => {
            dispatch(loading(false));
            dispatch(error(err.message || 'ERROR'));
        })

export const removeUserToken = () => dispatch =>
    AsyncStorage.removeItem('userToken')
        .then((data) => {
            dispatch(loading(false));
            dispatch(removeToken(data));
        })
        .catch((err) => {
            dispatch(loading(false));
            dispatch(error(err.message || 'ERROR'));
        })

reducers.js:

import { combineReducers } from 'redux';

const rootReducer = (state = {
    token: {},
    loading: true,
    error: null,
}, action) => {
    switch (action.type) {
        case 'GET_TOKEN':
            return { ...state, token: action.token };
        case 'SAVE_TOKEN':
            return { ...state, token: action.token };
        case 'REMOVE_TOKEN':
            return { ...state, token: action.token };
        case 'LOADING':
            return { ...state, loading: action.isLoading };
        case 'ERROR':
            return { ...state, error: action.error };
        default:
            return state;
    }
};

export default combineReducers({
    token: rootReducer
});

AuthLoading.js:

import React from 'react';
import {
    ActivityIndicator,
    AsyncStorage,
    StatusBar,
    StyleSheet,
    View,
} from 'react-native';
import { connect } from 'react-redux';
import { getUserToken } from '../actions';

class AuthLoadingScreen extends React.Component {
    static navigationOptions = {
        header: null,
    };
    constructor() {
        super();
    }

    componentDidMount() {
        this._bootstrapAsync();
    }

    // Fetch the token from storage then navigate to our appropriate place
    _bootstrapAsync = () => {

        this.props.getUserToken().then(() => {
            this.props.navigation.navigate(this.props.token.token !== null ? 'App' : 'Auth');
        })
            .catch(error => {
                this.setState({ error })
            })

    };

    // Render any loading content that you like here
    render() {
        return (
            <View style={styles.container}>
                <ActivityIndicator />
                <StatusBar barStyle="default" />
            </View>
        );
    }
}

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

const mapStateToProps = state => ({
    token: state.token,
});


const mapDispatchToProps = dispatch => ({
    getUserToken: () => dispatch(getUserToken()),
});

export default connect(mapStateToProps, mapDispatchToProps)(AuthLoadingScreen);

We can see from the above that our state is handled by redux and our token is also stored in redux. The below shows how our token can be gotten from redux store.

import React from 'react';
import {
    AsyncStorage,
    Button,
    StatusBar,
    StyleSheet,
    View,
    Text,
} from 'react-native';

import { connect } from 'react-redux';
import { removeUserToken } from '../actions';

class OtherScreen extends React.Component {
    static navigationOptions = {
        title: 'Lots of features here',
    };

    render() {
        return (
            <View style={styles.container}>
                <Button title="I'm done, sign me out" onPress={this._signOutAsync} />
                <StatusBar barStyle="default" />
                <Text>  Here is the user token {this.props.token.token}</Text>
            </View>
        );
    }

    _signOutAsync =  () => {
        this.props.removeUserToken()
            .then(() => {
                this.props.navigation.navigate('Auth');
            })
            .catch(error => {
                this.setState({ error })
            })
    };
}


const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#fff',
        alignItems: 'center',
        justifyContent: 'center',
    },
});

const mapStateToProps = state => ({
    token: state.token,
});

const mapDispatchToProps = dispatch => ({
    removeUserToken: () => dispatch(removeUserToken()),
});

export default connect(mapStateToProps, mapDispatchToProps)(OtherScreen);

The full source code for the redux integration can be found here.

Lets see how we can achieve the same thing with React Context-API.

We define provider.js like this:

React, { Component } from 'react';
import { AsyncStorage } from 'react-native';

export const MyContext = React.createContext();


export default class MyProvider extends Component {
    state = {
        token: '',
        saveToken: async () => {
            try {
                const resp = await AsyncStorage.setItem('userToken', 'abc');
                return resp;
            }
            catch (error) {
                this.setState({ error })
            }

        },
        removeToken: async () => {
            try {
                const resp = await AsyncStorage.removeItem('userToken');
                return resp
            }
            catch (error) {
                this.setState({ error })
            }
        },
        getToken: async () => {
            try {
                const resp = await AsyncStorage.getItem('userToken');
                return resp;
            }
            catch (error) {
                this.setState({ error })
            }
        }

    }


    componentWillMount() {
        AsyncStorage.getItem('userToken')
            .then((token) => {
                this.setState({ token })
            })
            .catch(error => {
                this.setState({ error })
            })
    }

    render() {
        return (
            <MyContext.Provider value={this.state}>
                {this.props.children}
            </MyContext.Provider>
        );
    }
}

We would need to wrap our parent container with the Provider component like this:

import React from 'react';
import { StyleSheet, View, Text } from 'react-native';
import { AppNavigator} from './navigator';
import MyProvider from './Provider';


export default () => (
  <MyProvider>
    <View style={styles.container}>
      <AppNavigator />
    </View>
  </MyProvider>
)

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
});

Our AuthLoadingScreen would look like this; it's quite complex so I advise you spend some time on this:

import React from 'react';
import {
    ActivityIndicator,
    AsyncStorage,
    StatusBar,
    StyleSheet,
    View,
} from 'react-native';
import { MyContext } from '../Provider';


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

class AuthLoadingScreen extends React.Component {
    static navigationOptions = {
        header: null,
    };
    constructor() {
        super();

    }
    componentDidMount() {
        this._bootstrapAsync();
    }

    // Fetch the token from storage then navigate to our appropriate place
    _bootstrapAsync = async () => {

        this.props.context.getToken()
            .then((userToken) => {
                // This will switch to the App screen or Auth screen and this loading
                // screen will be unmounted and thrown away.
                this.props.navigation.navigate(userToken ? 'App' : 'Auth');
            })
            .catch(error => {
                this.setState({ error });
            })

    };

    render() {
        return (
            <View style={styles.container}>
                <MyContext.Consumer>
                    {context => ((
                        <View>
                            <ActivityIndicator />
                            <StatusBar barStyle="default" />
                        </View>
                    ))}
                </MyContext.Consumer>
            </View>
        );
    }
};


const ForwardRef = React.forwardRef((props, ref) => (
    <MyContext.Consumer>
        {context => <AuthLoadingScreen context={context} {...props} />}
    </MyContext.Consumer>
));

export default ({ navigation }) => (
    <View style={styles.container}>
        <ForwardRef
            navigation={navigation}
        />
    </View>

)

Now we have our token saved in Context, let's see how we can get it!

import React from 'react';
import {
    AsyncStorage,
    Button,
    StatusBar,
    StyleSheet,
    View,
    Text,
} from 'react-native';
import { MyContext } from '../Provider';

export default class OtherScreen extends React.Component {
    static navigationOptions = {
        title: 'Lots of features here',
    };

    render() {
        return (
            <View style={styles.container}>
                <MyContext.Consumer>
                    {context => ((
                        <View>
                            <Button title="I'm done, sign me out" onPress={() => this._signOutAsyn(removeToken)} />
                            <Text> Here is your token  {context.token} </Text>
                            <StatusBar barStyle="default" />
                        </View>
                    ))}
                </MyContext.Consumer>
            </View>
        );
    }

    _signOutAsync = async (removeToken) => {
        removeToken()
            .then(() => {
                this.props.navigation.navigate('Auth');
            })
            .catch(error => {
                this.setState({ error })
            })
    };
}


const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#fff',
        alignItems: 'center',
        justifyContent: 'center',
    },
});

The full Context-API integration source code can be found here.

Wrapping Up

We have learned how to:

  1. Save our token in Redux and how to retrieve it
  2. Use React Context-API

You can find complete source code on Github here. Comments and feedback are well appreciated.

Originally published on blog.usejournal.com

Author's avatar
David Abimbola
Full stack javascript developer
    Python
    Objective-C
    Ruby
    C#
    CSS
    HTML
    Java
    Javascript
    React
    React-Native
    Nodejs

Related Jobs

Related Issues

viebel / klipse-clj
viebel / klipse-clj
  • Open
  • 0
  • 0
  • Intermediate
  • Clojure
viebel / klipse
  • Open
  • 0
  • 0
  • Intermediate
  • Clojure
viebel / klipse
  • Open
  • 0
  • 0
  • Intermediate
  • Clojure
  • $100
viebel / klipse
  • 1
  • 0
  • Intermediate
  • Clojure
viebel / klipse
  • Open
  • 0
  • 0
  • Intermediate
  • Clojure
  • $80
viebel / klipse
  • Open
  • 0
  • 0
  • Advanced
  • Clojure
  • $80
viebel / klipse
  • Started
  • 0
  • 2
  • Advanced
  • Clojure
  • $180
viebel / klipse
  • Open
  • 0
  • 0
  • Intermediate
  • Clojure
viebel / klipse
  • Started
  • 0
  • 1
  • Intermediate
  • Clojure
  • $80

Get hired!

Sign up now and apply for roles at companies that interest you.

Engineers who find a new job through JavaScript Works average a 15% increase in salary.

Start with GithubStart with Stack OverflowStart with Email