Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

react-boilerplate authentication login page flashes on page reload

I'm working on an app with a login page and the rest of the pages of the app (should be logged in to view). I'm using react-boilerplate. From this example, I edited my asyncInjectors.js file to have redirectToLogin and redirectToDashboard methods:

//asyncInjectors.js
export function redirectToLogin(store) {
  return (nextState, replaceState) => {
    const isAuthenticated = store.getState().get('app').get('isAuthenticated');

    if (!isAuthenticated) {
      replaceState({
        pathname: '/login',
        state: {
          nextPathname: nextState.location.pathname,
        },
      });
    }
  };
}

export function redirectToDashboard(store) {
  return (nextState, replaceState) => {
    const isAuthenticated = store.getState().get('app').get('isAuthenticated');

    if (isAuthenticated) {
      replaceState('/');
    }
  }
}

Then I just set the redirectToLogin as the onEnter of the pages and redirectToDashboard for the login page.

It works fine but when the page is refreshed (F5) when logged in, the login page renders briefly and then renders the actual page. The login page just dispatches an authenticate action in componentWillMount and then redirects in componentDidUpdate:

//login.js
componentWillMount() {
  this.props.dispatch(authenticate());
}

componentDidUpdate(prevProps, prevState) {
  if (this.props.isAuthenticated) {
    const nextPathname = prevProps.location.state ? prevProps.location.state.nextPathname : '/';

    browserHistory.push(nextPathname);
  }
}

The container for the pages also has the same componentWillMount code. Not sure if it's because of the sagas but here's the code:

//sagas.js
export function* login({ user, password }) {
    try {
        const token = yield call(app.authenticate, {
            strategy: 'local',
            user,
            password,
        });

        return token;
    } catch (error) {
        return onError(error);
    }
}

// For page refresh after logging in
export function* authenticate() {
    try {
        const token = yield call(app.authenticate);

        return token;
    } catch (error) {
        return onError(error);
    }
}

export function* logout() {
    try {
        const response = yield call(app.logout);

        return response;
    } catch (error) {
        return onError(error);
    }
}

export function* loginFlow() {
    while (true) {
        const request = yield take(LOGIN_REQUEST);
        const winner = yield race({
            auth: call(login, request.data),
            logout: take(LOGOUT_REQUEST),
        });

        if (winner.auth && winner.auth.accessToken) {
            yield put(actions.setAuthState(true));
        }
    }
}

export function* logoutFlow() {
    while (true) {
        yield take(LOGOUT_REQUEST);
        yield put(actions.setAuthState(false));
        yield call(logout);
        browserHistory.push('/login');
    }
}

export function* authenticateFlow() {
    while (true) {
        yield take(AUTHENTICATE);

        const response = yield call(authenticate);

        if (response && response.accessToken) {
            yield put(actions.setAuthState(true));
        }
    }
}

export default [
    loginFlow,
    logoutFlow,
    authenticateFlow,
];

How do I get rid of the flashing login page?

EDIT: When I tried gouroujo's answer, I couldn't logout.

//asyncInjectors.js
import jwtDecode from 'jwt-decode';

export function redirectToLogin(store) {
    return (nextState, replaceState, callback) => {
        const token = localStorage.token;

        if (token) {
            const jwt = jwtDecode(token);

            if (jwt.exp <= (new Date().getTime() / 1000)) {
                store.dispatch(actions.setAuthState(false));

                replaceState({
                    pathname: '/login',
                    state: {
                        nextPathname: nextState.location.pathname,
                    },
                });
            }
        }

        store.dispatch(actions.setAuthState(true));
        callback();
    };
}

When I hit refresh, the login page doesn't show but now I can't log out.

like image 307
dork Avatar asked May 12 '17 06:05

dork


1 Answers

You have two way to avoid flashing the login page on initial render : make your authenticate function synced or wait the answer with a loading page.

1- Check if your token is present and valid (expiration date) client-side to choose if you have to redirect the user to the login or the dashboard page first. When the answer come back from your server you correct your initial guess but in the vaste majority you won't need to.

user landing ->

  • check the token client-side -> redirect to login if needed
  • check the token server-side -> wait answer -> re-redirect if needed

To check the token client-side you have to check the local storage. For example:

class App extends Component {
  requireAuth = (nextState, replace) => {
    if (!localStorage.token) {
      replace({
        pathname: '/login',
        state: { nextPathname: nextState.location.pathname }
      })
    }
  }
  render() {
    return (
      <Router history={browserHistory}>
        <Route path="/login" component={LoginPage} />
        <Route
          path="/"
          component={AppLayout}
          onEnter={this.requireAuth}
        > ... </Route>
     )
   }
 }

If you use a token with a relatively short expiration date, you will also have to check the expiration date, thus decode the token.

try {
      const jwt = JWTdecode(token);
      if (moment().isBefore(moment.unix(jwt.exp))) {
        return nextState;
      } else {
       replace({
        pathname: '/login',
        state: { nextPathname: nextState.location.pathname }
       })
      }
    } catch (e) {
      replace({
        pathname: '/login',
        state: { nextPathname: nextState.location.pathname }
      })
    }

2- Show a loading screen before you receive the answer from the server. add some Css transition effect on opacity to avoid the "flash"

like image 139
gouroujo Avatar answered Nov 15 '22 04:11

gouroujo