reactjsauthenticationreduxreact-cookie

Make redux-auth-wrapper wait until session checked


so here is my auth.js code:

import locationHelperBuilder from 'redux-auth-wrapper/history4/locationHelper';
import { connectedRouterRedirect } from 'redux-auth-wrapper/history4/redirect';
import { createBrowserHistory } from 'history';

// import Spinner from '../components/layout/Spinner';

const locationHelper = locationHelperBuilder({});
createBrowserHistory();

export const UserIsAdmin = connectedRouterRedirect({
  wrapperDisplayName: 'UserIsAdmin',
//   AuthenticatingComponent: Spinner,
  redirectPath: (state, ownProps) => 
    locationHelper.getRedirectQueryParam(ownProps) || '/',
  allowRedirectBack: true,
  authenticatedSelector: state => state.user.isAuthenticated && state.user.isAdmin
});

export const UserIsAuthenticated = connectedRouterRedirect({
  wrapperDisplayName: 'UserIsAuthenticated',
//   AuthenticatingComponent: Spinner,
  redirectPath: (state, ownProps) =>
    locationHelper.getRedirectQueryParam(ownProps) || '/',
  allowRedirectBack: true,
  authenticatedSelector: state => state.user.isAuthenticated
});

export const UserIsNotAuthenticated = connectedRouterRedirect({
  wrapperDisplayName: 'UserIsNotAuthenticated',
//   AuthenticatingComponent: Spinner,
  redirectPath: (state, ownProps) =>
    locationHelper.getRedirectQueryParam(ownProps) || '/',
  allowRedirectBack: true,
  authenticatedSelector: state => !state.user.isAuthenticated
});

and here is where i need to make redux-auth-wrapper to wait until I update the state with the user data to send him wherever he was before refreshing the page:

const MainRoutes = ( { cookies } ) => {
    // state
    const { isAuthenticated } = useSelector( state => state.user );

    // dispatch
    const dispatch = useDispatch();

    const login = () => dispatch( loginAction() );
    const logout = () => dispatch( logoutAction() );

    // check if session is active ( cookie ) 
    useEffect(() => {
        if( !isAuthenticated ) {

            const checkSession = async () => {
                const cookie = cookies.get('token');
                if( cookie && cookie.trim() !== '' ) {
                    axiosClient.defaults.headers.Authorization = `Bearer ${ cookie }`;
                    login();
                } else logout();
            };

            checkSession()
        }

        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [ cookies, isAuthenticated ]);

    return (  
        <Switch>
            <Route exact path="/" component={ Courses } />

            <Route path="/admin" component={  UserIsAdmin( Admin )  } />
            <Route path="/profile" component={  UserIsAuthenticated( Profile )  } />

            <Route exact path="/login" component={ UserIsNotAuthenticated( Login ) } />
            <Route exact path="/signin" component={ UserIsNotAuthenticated( Signin ) } />
            <Route exact path="/send-email" component={ UserIsNotAuthenticated( Email ) } />
            <Route exact path="/recover" component={ UserIsNotAuthenticated( Recover ) } />

            <Route exact path="/policy" component={ Policy } />
            <Route exact path="/usage" component={ Usage } />
            <Route exact path="/faqs" component={ FAQS } />
        </Switch>
    );
}

export default withRouter(withCookies(MainRoutes));

Here bassically I check if exists a session cookie, soy I auto log the user in. The problem is, that when I go to some route (for example: /admin, which is protected, and therefore is being supervised by redux-auth.wrapper), and I refresh the page, it always sends me back to '/', because the check of the isAuthenticated and isAdmin is done before my MainRoutes component can log the user in, which of course fails the check in the authenticated selector of the auth.js, and sends me to '/'. My first idea to solve this was to store those 2 flags in the localStorage, so I will be driven to the previous path even if my user didn't finished logging in. But I was wondering if there is any way to specificaly say to redux-auth-wrapper, to wait until my useEffect function has finished.

Thank you.


Solution

  • This should hold the first render and give the component a chance to verify the login status, give it a try.

    But I was wondering if there is any way to specificaly say to redux-auth-wrapper, to wait until my useEffect function has finished.

    Note: the solution is not specific to redux-auth-wrapper.

    const MainRoutes = ( { cookies } ) => {
    
        const { isAuthenticated } = useSelector( state => state.user );
    
        /* use a state to hold the render */
        const [isFirstRender, setFirstRender] = useState(true)
    
        const dispatch = useDispatch();
    
        const login = () => dispatch( loginAction() );
        const logout = () => dispatch( logoutAction() );
    
        /* after the first render the user should be logged in (if previously was) */
        useEffect(() => {
            setFirstRender(false)
        }, [])
    
        useEffect(() => {
            if( !isAuthenticated ) {
    
                const checkSession = async () => {
                    const cookie = cookies.get('token');
                    if( cookie && cookie.trim() !== '' ) {
                        axiosClient.defaults.headers.Authorization = `Bearer ${ cookie }`;
                        login();
                    } else logout();
                };
    
                checkSession()
            }
    
            // eslint-disable-next-line react-hooks/exhaustive-deps
        }, [ cookies, isAuthenticated ]);
    
        /* If the effect checking the auth runs fast you can leave 
        the return as this, otherwise you might want to show a loading 
        indicator */
        return (
            <>  
                {!isFirstRender &&
                    <Switch>
                        <Route exact path="/" component={ Courses } />
                        <Route path="/admin" component={  UserIsAdmin( Admin )  } />
                        <Route path="/profile" component={  UserIsAuthenticated( Profile )  } />
                        <Route exact path="/login" component={ UserIsNotAuthenticated( Login ) } />
                        <Route exact path="/signin" component={ UserIsNotAuthenticated( Signin ) } />
                        <Route exact path="/send-email" component={ UserIsNotAuthenticated( Email ) } />
                        <Route exact path="/recover" component={ UserIsNotAuthenticated( Recover ) } />
                        <Route exact path="/policy" component={ Policy } />
                        <Route exact path="/usage" component={ Usage } />
                        <Route exact path="/faqs" component={ FAQS } />
                    </Switch>
                }
            </>
        );
    }
    
    export default withRouter(withCookies(MainRoutes));