Redirecting user back to protected page after signing (React/Redux)


In developing web applications it’s a common practice to redirect user to login page if he tries to visit protected route. It’s also cool to redirect user back to protected route after he has successfully authenticated.

My approach in React/Redux is to (1) store a route endpoint in Redux state when unauthorized user attempts to access protected route. He is than redirected to login page. And (2) after successful authentication it’s checked if there’s an endpoint to which to redirect. If there is one, redirect to that endpoint and delete it in Redux state.

First step is implemented in higher-order function that wraps component that needs to be protected from aunauthorized access. There I connect a component to Redux store and in component’s componentWillMount checking if authentication data is present and if not, endpoint is noted and user redirected to login page.

const ifAuthenticated = endpoint => WrappedComponent => {

    class Authenticate extends React.Component {
        componentWillMount() {
            const {auth, history, setNextRoute} = this.props
            if (!auth.id) {
                history.push('/login')
                setNextRoute(endpoint)
            }
        }
        render() {
            return <WrappedComponent {...this.props} />
        }
    }

    return connect(_.pick('auth'), {setNextRoute})(Authenticate)
}

_.pick comes from lodash/fp and this.props.history from React-Router.

Second step is done in async action that performs login request to API. I use redux-thunk here but mental model is the same if you use other middleware for async actions (like redux-observable). Also it’s worth mentioning that for API calls promise-based client is used. After successful sign-in next route is returned. It’s important we get its value before calling api method because later value might be different (because of async).

export const submitLoginForm = data => (dispatch, getState, api) => {
    const {nextRoute} = getState().login
    return api.loginUser(data)
        .then(res => {
            return {
                nextRoute
            }
        }
})

Side note: in above snippet we get api parameter by defining extra argument to redux-thunk when initializing Redux store. Read about it here. Or watch this video where getState parameter is explained too.

We still need to redirect user back to protected route he attempted to visit as unauthorized user. I moved this piece of functionality from action to component because it feels better and we have an access to history object for redirecting via withRouter decorator (it comes from React-Router and we only need to wrap top-level component to gain access to history object).

const mapDispatchToProps = (dispatch, ownProps) => {
    return {
        submit: form => e => {
            e.preventDefault()

            // ...

            dispatch(submitLoginForm(form)).then(data => {
                ownProps.history.push(data.nextRoute || '/')
                if (data.nextRoute) {
                    dispatch(setNextRoute(undefined))
                }
            })
        }
    }
}

We dispatch an action that sets next route to undefined if next route is present. That’s why we needed to obtain its value as early as possible in previous snippet because if we did later, it would be undefined because action dispatch(setNextRoute(undefined)) gets executed before obtaining next route value say in successful callback response from server.