Reputation: 81
I am writing a small to-do app and am having problems with rerouting a user based on data from the backend. The app is using:
express-session
to create a server-side user sessionconnect-mongodb-session
to store the session datareact-redux
for application state managementWhen a user logs in, a User
model property called isLoggedIn
is set to true
. This value is used to redirect the user to the LandingPage
component where they can create new to-dos. This is fine but the problem is that if the landing page is refreshed, the value for isLoggedIn
has not arrived from the backend before LandingPage
renders. This causes the user to be rerouted to the login page, then the data arrives and then they are rerouted to the landing page again.
The login page "flashing" up is extremely annoying and I can't find any way of getting round it. I tried storing a boolean value isLoggedIn
in localStorage
and this worked great but surely that defeats the point of using a server-side session?
Here are some snippets from my code, thanks
App.js
import { getUser } from "./redux/actions/auth";
const App = withRouter(({ user, data }) => {
useEffect(() => {
store.dispatch(getUser()); // user data loaded every time app re-renders
}, []);
return (
<Provider store={store}>
<Router>
<div className='my-app'>
<Switch>
<Route exact path='/login' component={Login} />
<Private
isLoggedIn={isLoggedIn}
path='/landing'
component={LandingPage}
/>
</Switch>
</div>
</Router>
</Provider>
);
const Private = ({ isLoggedIn, component: Component, ...rest }) => {
return (
<Route
{...rest}
render={props =>
isLoggedIn ? (
<Component {...props} />
) : (
// I think the problem is here - because isLoggedIn is not available from the backend
// call yet, so login page gets rendered before the data arrives
<Redirect to='/login' {...props} />
)
}
/>
);
};
Login.js
render() {
const { user } = this.props;
if (user.isLoggedIn) return <Redirect to='/landing' />; // user sent to landing if they are logged in
return (... login form jsx)
}
Login.propTypes = {
user: PropTypes.object
};
const mapStateToProps = state => ({
user: state.auth.user
});
export default connect(mapStateToProps)(Login);
getUser.js
export const getUser = () => async dispatch => {
try {
const response = await axios.get("/api/user");
dispatch({
type: LOADED_USER,
payload: response.data
});
} catch (error) {
dispatch({
type: LOAD_USER_FAILED
});
}
};
getUserReducer.js
const initialState = {
user: {}
};
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case LOGIN_SUCCEEDED:
case LOADED_USER:
return {
...state,
user: payload
};
case LOGIN_FAILED:
case LOAD_USER_FAILED:
return {
...state,
user: {}
};
default:
return state;
}
}
Upvotes: 1
Views: 95
Reputation: 1238
So there's a few ways of doing this. One way would be to create an authenticated route that's only purpose is to validate the session cookie like /me
for example.
If the user has logged in previously, the browser should have a valid session as a cookie. This would mean that /me
can be requested with a 200. If the session is expired then you'll get a 401. In the case of a 401 you just redirect back to the login page.
There's lots of ways to communicate that this is happening to the user as well. One way is to have some state in local storage like you're already doing and just make the assumption that the user is allowed to navigate to the landing page. You wouldn't be loading any sensitive data anyway since the data should be behind authenticated endpoints.
In the case of any 401s you can redirect to login, or show an error to the user saying something like "It appears you're session has expired, click here to log back in.".
Upvotes: 1