Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling Refresh Token in React Native

I have an app authenticating fine and returning the access_token and refresh_token. I store them with AsyncStorage and save/get the access_token with redux. This is the very first app I am building and I am struggling with how and where to use the refresh_token.

This is the axios call in the component loginForm.js

axios({
                url: `${base}/oauth/token`,
                method: 'POST',
                data: formData,
                headers: {
                    Accept: 'application/json',
                    'Content-Type': 'multipart/form-data',
                }
            })
            .then(response => {
                setStatus({ succeeded: true });
                // console.log(response.data);
                deviceStorage.saveKey("userToken", response.data.access_token);
                deviceStorage.saveKey("refreshToken", response.data.refresh_token);
                Actions.main();
            })
            .catch(error => {
                if (error.response) {
                    console.log(error);
                }
            });

This is the service deviceStorage.js

import { AsyncStorage } from 'react-native';

const deviceStorage = {
    async saveItem(key, value) {
        try {
            await AsyncStorage.setItem(key, value);
        } catch (error) {
            console.log('AsyncStorage Error: ' + error.message);
        }
    }
};

export default deviceStorage;

This is the token action file

import { AsyncStorage } from 'react-native';
import {
    GET_TOKEN,
    SAVE_TOKEN,
    REMOVE_TOKEN,
    LOADING_TOKEN,
    ERROR_TOKEN
} from '../types';

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_TOKEN,
    isLoading: bool,
});

export const error = tokenError => ({
    type: ERROR_TOKEN,
    tokenError,
});

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', data)
        .then(() => {
            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'));
        });

This is the token reducer file

import {
    GET_TOKEN,
    SAVE_TOKEN,
    REMOVE_TOKEN,
    LOADING_TOKEN,
    ERROR_TOKEN
} from '../actions/types';

const INITIAL_STATE = {
    token: {},
    loading: true,
    error: null
};

export default (state = INITIAL_STATE, 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_TOKEN:
            return {
                ...state,
                loading: action.isLoading
            };
        case ERROR_TOKEN:
            return {
                ...state,
                error: action.error
            };
        default:
            return state;
    }
};

And this is the authentication file

import React from 'react';
import {
    StatusBar,
    StyleSheet,
    View,
} from 'react-native';
import { connect } from 'react-redux';
import { Actions } from 'react-native-router-flux';
import { Spinner } from '../common';
import { getUserToken } from '../../actions';

class AuthLoadingScreen extends React.Component {

    componentDidMount() {
        this.bootstrapAsync();
    }

    bootstrapAsync = () => {
        this.props.getUserToken().then(() => {
            if (this.props.token.token !== null) {
                Actions.main();
            } else {
                Actions.auth();
            }
        })
            .catch(error => {
                this.setState({ error });
            });
    };

    render() {
        return (
            <View style={styles.container}>
                <Spinner />
                <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);

I believe I need to create an action and reducer to get the refresh_token (is that correct?) but I do not know what to do with it and where to call it (perhaps in the authentication file?). Any help with this possibly with code examples related to my code would be massively appreciated. Thanks

like image 525
Jeff Avatar asked Sep 22 '19 16:09

Jeff


1 Answers

Below are the steps

Do Login , get accessToken , refreshToken from response and save it to AsyncStorage. Make common function for API calling

    async function makeRequest(method, url, params, type) {
      const token = await AsyncStorage.getItem('access_token');
    
      let options = {
        method: method,
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/json',
          Authorization: 'Bearer ' + token,
        },
      };
    
      if (!token) {
        delete options['Authorization'];
      }
    
      if (['GET', 'OPTIONS'].includes(method)) {
        url += (url.indexOf('?') === -1 ? '?' : '&') + queryParams(params);
      } else {
        Object.assign(options, {body: JSON.stringify(params)});
      }
    
      const response = fetch(ENV.API_URL+url, options);
    
      return response;
    }

Make one method in redux for getAceessTokenFromRefreshToken. Use this method when session is expired

How do you know session is expired?

From each API calling if you get response like (440 response code) in


    async componentWillReceiveProps(nextProps) {
        if (nextProps.followResponse && nextProps.followResponse != this.props.followResponse) {
          if (nextProps.followResponse.status) {
          
      if (nextProps.followResponse.status == 440) {
                // call here get acceesstokenfrom refresh token method and save again accesstoken in asyncstorage and continue calling to API
            }
          }
        }
      }

like image 130
Rajesh Nasit Avatar answered Oct 02 '22 09:10

Rajesh Nasit