Search code examples
javascriptreactjsexpressreduxisomorphic-javascript

How to access cookie in server-side data fetch for isomorphic React app


I'm building an isomorphic/universal React + Redux + Express app. My server-side data fetching is pretty standard: I see what routes match the URL, call the relevant data-fetching methods that all return promises, then wait for them to resolve and render the HTML.

My challenge: Some of the API calls require auth. Logged-in users have a cookie, which of course gets sent with every request. But when I'm doing data fetching on the server side to populate the store for initial render, the cookie isn't available to my API calls. How do I make that happen?

// server-entry.jsx
app.get('*', (req, res) => {
  const store = createStore(
    combineReducers({
      // lots of reducers
    }),
    {},
    applyMiddleware(thunk),
  );
  /*
    The contents of getDataFetchers isn't important. All you need
    to know is it returns an array of data-fetching promises like so:

    dispatch(thunkAction());
  */
  const fetchers = getDataFetchers(req.url, store);

  Promise.all(fetchers).then(() => {
    // render the tree
  });
});

// one of my thunk actions hits an API endpoint looking like this:
app.get('/api', (req, res) => {
  // this is simplified, but you get the idea:
  // we need access to the cookie to authenticate
  // but when this call is made server-side, req.session.user doesn't exist
  if (!req.session.user) res.status(403);
  else res.json({ data: 'here' });
});

I guess I could grab the cookie from req in app.get and pass is through as an argument all the way to the actual data fetch (done with axios), where it would be specified with the Cookie header. But that feels gross.


Solution

  • What you suggested is correct, if you'd like your server-side HTTP requests to contain the original request's cookies, you need to construct your HTTP client (axios in your case) in a way that allows it to have access to headers either on instantiation or before making each request.

    Here is a link to how Apollo does it.

    import { ApolloProvider } from 'react-apollo';
    import { ApolloClient } from 'apollo-client';
    import { createHttpLink } from 'apollo-link-http';
    import Express from 'express';
    import { StaticRouter } from 'react-router';
    import { InMemoryCache } from "apollo-cache-inmemory";
    
    import Layout from './routes/Layout';
    
    // Note you don't have to use any particular http server, but
    // we're using Express in this example
    const app = new Express();
    app.use((req, res) => {
    
      const client = new ApolloClient({
        ssrMode: true,
        // Remember that this is the interface the SSR server will use to connect to the
        // API server, so we need to ensure it isn't firewalled, etc
        link: createHttpLink({
          uri: 'http://localhost:3010',
          credentials: 'same-origin',
          headers: {
            cookie: req.header('Cookie'),
          },
        }),
        cache: new InMemoryCache(),
      });
    
      const context = {};
    
      // The client-side App will instead use <BrowserRouter>
      const App = (
        <ApolloProvider client={client}>
          <StaticRouter location={req.url} context={context}>
            <Layout />
          </StaticRouter>
        </ApolloProvider>
      );
    
      // rendering code (see below)
    });
    

    As you can see, the ApolloClient is created and passed the initial request's cookies on instantiation.