0

So I have my protected routes enclosed in a HOC that first checks if the person is authenticated by seeing if they have a token. If they have a token, it is sent to the server to see if it is valid. If it is valid, it is refreshed (if it is < 15 minutes old). During this process the state of PERMIT_RENDER is set true if the token is valid allowing the component to render if it is; if not, it is just a loading screen until the server responds. Hopefully this is clear in the below code.

Anyway, with my current setup, when you first login it brings you to /home which is a protected route. You can click on links in the .nav to go between other protected routes. The problem is permitRender work properly when you first login and shows the loading until the server responds and brings you to /home. At this point it seems like permitRender should be setback to false so that when you click on a link to a protected route, it doesn't load until the server responds and the user just gets the loading screen until then. However, permitRender once true stays that way unless you refresh the screen. In addition, it automatically navigates to the other protected route before receiving the server response.

Basically, what is the correct way to set permitRender back to false after mounting or updating so that my loading component is triggered every time someone navigates to a protected route?

Some things that seemed to intuitive to me (mind you not knowing React and hwo to use setState that well) in componentDidMount(), componentDidUpdate(), componentDidMount(...state), etc.:

this.setState(this.state.auth.permitRender = false);

Which results in cannot read property 'auth' of null.

// ./helpers/require_auth.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { authRequired, refreshToken } from '../actions/authentication';
import Loading from './loading';

// This HOC not only protects certain routes by checking for existence of a token,
// but if the token exists it make sure it is current
// if it is current, it will refresh the token
export default function(ComposedComponent) {
    class AuthenticationRequired extends Component {

        // Check if the user has a token when protected route component is about to mount
        // Route them to / if they do not
        componentWillMount() {
            if (!sessionStorage.getItem('token')) {
                this.props.authRequired();
            } else {
                this.props.refreshToken();
            }
        }

        // If the component will be updated then also check if user is authorized
        componentWillUpdate() {
            if (!sessionStorage.getItem('token')) {
                this.props.authRequired();
            } else {
                this.props.refreshToken();
            }
        }

        // Render the component if passing these checks
        render() {
            if (this.props.permitRender) {
                return <ComposedComponent {...this.props} />            
            } else if (!this.props.permitRender) {
                return <Loading />
            }
        }
    }

    // 
    function mapStateToProps(state) {
        return { 
            permitRender: state.auth.permitRender
        };
    }

    // Conenct to the authRequired action
    return connect(mapStateToProps, { authRequired, refreshToken })(AuthenticationRequired);
}

// ./actions/authentication
import axios from 'axios';
import { push } from 'react-router-redux';
import { ROOT_URL } from '../../config/config.json';

// Establish the different types
export const AUTH_USER = 'auth_user';
export const UNAUTH_USER = 'unauth_user';
export const PERMIT_RENDER = 'permit_render';

// Refresh the token based on the users activity
export function refreshToken() {
    return function(dispatch) {
        axios
            .post(
                `${ROOT_URL}/api/auth/refresh/`, 
                { 'token': sessionStorage.getItem('token') }, 
                { headers: { 
                    'Content-Type': 'application/json',
                }
            })
            .then(response => {
                sessionStorage.setItem('token', response.data.token);
                dispatch({ type: AUTH_USER });
                dispatch({ type: PERMIT_RENDER });
            })
            .catch(error => {
                sessionStorage.clear();
                dispatch({ type: UNAUTH_USER });  
                dispatch(push('/'));
                dispatch(authError('This session has expired. Please login to continue browsing.')); 
            });            
    }
}

// ./reducers/authentication.js
import {
    AUTH_USER,
    UNAUTH_USER,
    PERMIT_RENDER,
} from '../actions/authentication';

export default function(state = {}, action) {
    switch(action.type) {
        case AUTH_USER:
            return { ...state, error: '', authenticated: true };
        case UNAUTH_USER:
            return { ...state, authenticated: false };
        case PERMIT_RENDER:
            return { ...state, permitRender: true };
        default:
            return state;
    }

    return state;
}

// ./reducers/index.js
import { combineReducers } from 'redux';
import { reducer as form } from 'redux-form';
import { routerReducer } from 'react-router-redux';
import authReducer from './authentication';
import helpReducer from './help';

const rootReducer = combineReducers({
    form,
    router: routerReducer,
    auth: authReducer,
    help: helpReducer
});

export default rootReducer;
cjones
  • 8,384
  • 17
  • 81
  • 175
  • You should check my answer, I have already detailed the answer: https://stackoverflow.com/questions/47063287/in-terms-of-a-stateless-front-end-client-how-secure-is-this-jwt-logic/47063929#47063929 . If you need more detail or have any question, I'll post an answer here :) – yuantonito Nov 03 '17 at 16:33

1 Answers1

0

Looking through the post that yuantonito shared here:

In terms of a stateless front-end client, how secure is this JWT logic?

It occurred to me I could do the following:

// ./require_auth.js
import { unpermitRender } from '..actions/'

....
componentWillUnmount() {
    this.props.unpermitRender();
}
....

// ./actions/authentication.js
export function unpermitRender() {
    return function(dispatch) {
        dispatch({ 
            type: PERMIT_RENDER,
            payload: false
        });         
    };
}

I then modified anywhere else in my ./actions/authentication.js that was using:

dispatch({ type: PERMIT_RENDER })

To:

dispatch({ type: PERMIT_RENDER, payload: true }) // or false

Which meant I had to modify the reducer to:

// ./reducers/authentication.js
...
case PERMIT_RENDER:
    return { ...state, permitRender: action.payload };
...

Now I am getting the loading screen navigating between protected routes and rendering only when server response has been received.

cjones
  • 8,384
  • 17
  • 81
  • 175