MDalt MDalt - 18 days ago 5
React JSX Question

Getting session cookie data on initial page load following a redirect from a server (Node and React)

I am trying to build a token system to allow authentication via an email link. The flow I am thinking might work is...

click email link (of the form site.com/login?token=hf74hf64&email=m@email.com) -> server checks the token is valid and the email is registered -> server redirects to '/' with a session cookie -> client acknowledges session cookie and authenticates the user

The last step is where I'm having trouble. How do I detect from within my component that a session cookie is present?

I was thinking of something like this in my React auth component:

class AuthenticatedComponent extends Component {
componentWillMount() {
if (cookie) {
this.props.dispatch(authenticateUser())//.....
}
}
}


Might this work, or do I need to make a separate
fetch
to the server and trigger the dispatch depending on the response?

Answer

We've implemented a very similar approach for our app. For this to work, we handle all the login in Node and not in the actual components.

  1. Check if token is provided in query string
  2. Pass token to server to validate
  3. If token is valid, create the cookie as you would for a normal user/pass login
  4. Redirect call to original url, sans the token

server.js

// I abstracted the login functionality into one call since it's the same for us
var handleAuthRequest = function handleAuthRequest(auth_body, req, res, next) {
  request ({
    method: 'POST',
    uri: Constants.API_LOGIN_URL,
    body: auth_body,
    json: true
  }, (error, response, body) => {
    if (response.statusCode === 200) {
      // this makes a cookie with the response of the body (auth token)
      ssoUtils.generateCookies(body, res)
      // this redirects to the initial url, with the provided cookie. 
      // Assuming your router already doesn't allow certain components to be accessed 
      // without a cookie, it would work the same for the login_token auth too.
      res.redirect(req.url)
    }
    else {
      next();
    }
  })
}

// this needs to come before any other routes
app.use((req, res, next) => {
// check if login_token query string was provided
if (req.query.hasOwnProperty('login_token')) {
  var {login_token} = req.query
  // API call to server to validate token
  var jwtToken = jwt.sign({
    sub: login_token
  }, Constants.API_JWT_SECRET)
  // modify the redirect url to remove the token
  let parsed = url.parse(req.url)
  delete req.query['login_token']
  let newUrl = parsed.pathname + '?' + qs.stringify(req.query)
  req.url = newUrl
  // call the generic login handler
  return handleAuthRequest({link_token: jwtToken}, req, res, next)
} 

Assuming your server will return the same response from logging in or a valid link token, this would just redirect the call back to whatever your existing process is so no separate functionality client side is needed. As you can see, we also sign the token in a JWT to ensure it's only accepted by the server if sent from our app.

We use React Router to handle our client side routing. Your onEnter check for the initial route would look like this.

routes.js

// token is passed in from our cookie by both the client and server 
module.exports = function (token, userAgent, originalUrl) {
  function isToken() {
    return token !== undefined && token !== null;
  }
  function ifNoTokenRedirect(nextState, replaceState) {
  // check if token is set from cookie
    if (!isToken()) {
      replaceState({ nextPathname: nextState.location.pathname}, '/signup?  redirect=' + originalUrl.pathname);
    }
  }
  return (
    // the actual routes
  )
}