Search code examples
reactjsserver-sidereactjs-fluxreact-router

React-router, JWT Cookie, Flux, Authentification & SSR


Hello StackOverflow community !

My application background is a bit complex but I feel like my problem isn't. So I have a debut of application which consist of a Sign Up / Log In / Private component. My routes.js contains the following

// routes.js
function requireAuth(nextState, transition) {
  if (!LoginStore.isLoggedIn()) {
    transition.to('/login', null, { nextPathname: nextState.location.pathname });
  }
}
<Route component={require('./components/App')} >
  <Route path="/" component={require('./components/Home')} />
  <Route path="/login" component={require('./components/LogIn')} />
  <Route path="/signup" component={require('./components/SignUp')} />
  <Route path="/private" component={require('./components/Private')} onEnter={requireAuth}/>
</Route>

Log In component retrieve from an API a JWT, store it in a LoginStore component (Flux design) and in a Cookie in order to have access to the Private component without re-login later on. The whole is working correctly as when I'm logged, I have access to the Private component and I can access it when refreshing (thanks to the cookie).

My problem comes from the fact that I am also rendering this solution on the server and if I try to access directly to /private (by directly I mean my first call on the application is /private), I am redirected to /login and then I can access to /private. I would like to fall on /private at the first call.

// server.js
var location = new Location(req.path, req.query);
  Router.run(routes, location, (error, initialState, transition) => {
    console.log(initialState);
      if (transition.isCancelled) {
        return res.redirect(302, transition.redirectInfo.pathname);
      } else {
        var html = React.renderToString(<Router {...initialState}/>);
        res.send(renderFullPage(html));
      }
  });

My LoginStore should retrieve the cookie and allow the access to the Private component but he doesn't succeed because my cookie cannot be find yet.

GET http://localhost/private [HTTP/1.1 302 Moved Temporarily 30ms]
GET http://localhost/login [HTTP/1.1 200 OK 51ms]
GET http://localhost/bundle.js [HTTP/1.1 200 OK 30ms]

I feel like I should send my cookie to the Router in the server.js so the LoginStore can be set but I don't know much about how to do it and it may not be the best solution. I would really appreciate help on this problem.

Thanks you in advance.


Solution

  • Your solution is similar to mine. Use react-cookie for manipulating cookies and patch save/delete method on the server on each request before calling Router.run. Also make sure your routes are sync.

    import Iso from 'iso';
    import React from 'react';
    import ReactDomServer from 'react-dom/server';
    import Router from 'react-router';
    import Location from 'react-router/lib/Location';
    import cookie from 'react-cookie';
    import routes from '../../app/routes';
    import Alt from '../../app/lib/Alt';
    import AltBootstrap from '../lib/AltBootstrap';
    
    export default {render};
    
    function render(req, res, next) {
      cookie.setRawCookie(req.headers.cookie); // THIS
      cookie.save = res.cookie.bind(res); // THIS
    
      let location = new Location(req.path, req.query);
      Router.run(routes, location, (error, state, transition) => {
        if (error) return next(error);
        if (transition.isCancelled) return res.redirect(transition.redirectInfo.pathname);
    
        AltBootstrap.run(state, req).then(snapshot => {
          Alt.bootstrap(snapshot);
    
          let markup = ReactDomServer.renderToString(<Router {...state}/>);
          let html = Iso.render(markup, Alt.flush());
          res.render('index', {html});
        }).catch(next);
      });
    }
    

    A complete source code is available here: isomorphic-react-flux-boilerplate.