Hashnode/mern-starter

Integration with Passport - Multiple Requests

judylin1 opened this issue · 1 comments

I'm trying to add Passport.JS to my MERN project, but deserializeUser keeps getting called twice. Both times I can see my user object, but the first time req.user is undefined, but the second time it's correct. Why is it getting called twice and why does only the second time work? Does anyone have a working example with Passport?

Additional details:
MERN: 2.0.0
passport: 0.4.0
express-session: 1.15.6

Userobject from both deserializeUser calls:
{ userId: '1234', firstName: 'Joe', lastName: 'Smith', email: 'asdf@aol.com' }

Server.js:
`import Express from 'express';
import compression from 'compression';
import bodyParser from 'body-parser';
import path from 'path';
const passport = require('passport');
const cookieParser = require('cookie-parser');
const expressSession = require('express-session');
const MongoStore = require('connect-mongo')(expressSession);

// Webpack Requirements
import webpack from 'webpack';
import config from '../webpack.config.dev';
import webpackDevMiddleware from 'webpack-dev-middleware';
import webpackHotMiddleware from 'webpack-hot-middleware';

// Initialize the Express App
const app = new Express();

// Set Development modes checks
const isDevMode = process.env.NODE_ENV === 'development' || false;
const isProdMode = process.env.NODE_ENV === 'production' || false;

// Run Webpack dev server in development mode
if (isDevMode) {
const compiler = webpack(config);
app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath:
config.output.publicPath }));
app.use(webpackHotMiddleware(compiler));
}

// React And Redux Setup
import { configureStore } from '../client/store';
import { Provider } from 'react-redux';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { match, RouterContext } from 'react-router';
import Helmet from 'react-helmet';

import routes from '../client/routes';
import { fetchComponentData } from './util/fetchData';
import serverConfig from './config';
import serverRoutes from './routes/routes';

app.use(compression());
app.use(Express.static(path.resolve(__dirname, '../dist/client')));
app.use(cookieParser('somethingsomething'));
app.use(bodyParser.json({ limit: '20mb' }));
app.use(bodyParser.urlencoded({ limit: '20mb', extended: false }));

require('./passportConfig')(passport);
app.use(expressSession({
name: 'testApp',
secret: 'somethingsomething',
resave: true,
saveUninitialized: false,
store: new MongoStore({ url: process.env.DBURL }),
cookie: {
maxAge: 36000 * 24 * 14,
secure: false,
},
}));
app.use(passport.initialize());
app.use(passport.session());
app.use((req, res, next) => {
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});

app.use('/api', serverRoutes);

// Render Initial HTML
const renderFullPage = (html, initialState) => {
const head = Helmet.rewind();

// Import Manifests
const assetsManifest = process.env.webpackAssets && JSON.parse(process.env.webpackAssets);
const chunkManifest = process.env.webpackChunkAssets && JSON.parse(process.env.webpackChunkAssets);

return `
<!doctype html>


${head.base.toString()}
${head.title.toString()}
${head.meta.toString()}
${head.link.toString()}
${head.script.toString()}

    ${isProdMode ? `<link rel='stylesheet' href='${assetsManifest['/app.css']}' />` : ''}
    <link href='https://fonts.googleapis.com/css?family=Lato:400,300,700' rel='stylesheet' type='text/css'/>
    <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.12/semantic.min.css"></link>
    <link rel="shortcut icon" href="http://res.cloudinary.com/hashnode/image/upload/v1455629445/static_imgs/mern/mern-favicon-circle-fill.png" type="image/png" />
  </head>
  <body>
    <div id="root"><div>${html}</div></div>
    <script>
      window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};
      ${isProdMode ?
      `//<![CDATA[
      window.webpackManifest = ${JSON.stringify(chunkManifest)};
      //]]>` : ''}
    </script>
    <script src='${isProdMode ? assetsManifest['/vendor.js'] : '/vendor.js'}'></script>
    <script src='${isProdMode ? assetsManifest['/app.js'] : '/app.js'}'></script>
  </body>
</html>

`;
};

const renderError = err => {
const softTab = ' ';
const errTrace = isProdMode ?
:<br><br><pre style="color:red">${softTab}${err.stack.replace(/\n/g,
${softTab})}</pre> : '';
return renderFullPage(Server Error${errTrace}, {});
};

// Server Side Rendering based on routes matched by React-router.
app.use((req, res, next) => {
match({ routes, location: req.url }, (err, redirectLocation, renderProps) => {
if (err) {
return res.status(500).end(renderError(err));
}

if (redirectLocation) {
  return res.redirect(302, redirectLocation.pathname + redirectLocation.search);
}

if (!renderProps) {
  return next();
}

const store = configureStore();

return fetchComponentData(store, renderProps.components, renderProps.params)
  .then(() => {
    const initialView = renderToString(
      <Provider store={store}>
        <RouterContext {...renderProps} />
      </Provider>
    );
    const finalState = store.getState();

    res
      .set('Content-Type', 'text/html')
      .status(200)
      .end(renderFullPage(initialView, finalState));
  })
  .catch((error) => next(error));

});
});

// start app
app.listen(serverConfig.port, (error) => {
if (!error) {
console.log(MERN is running on port: ${serverConfig.port}! Build something amazing!); // eslint-disable-line
}
});

export default app;`

PassportConfig.js:
`const LocalStrategy = require('passport-local').Strategy;
const request = require('request');
const apiUrl = process.env.API_URL;
const token = process.env.TOKEN;
const User = require('./models/user');
import Config from './config';

const API_URL = (typeof window === 'undefined' || process.env.NODE_ENV === 'test') ?
process.env.BASE_URL || (http://localhost:${process.env.PORT || Config.port}/api) : '/api';

module.exports = (passport) => {
passport.serializeUser((userObject, done) => {
done(null, userObject);
});

passport.deserializeUser((userObject, done) => {
if (userObject.adminUser) done(null, userObject.adminUser);
else done(null, userObject);
});

passport.use('local-login', new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true, // allows us to pass back the entire request to the callback
}, (req, email, password, done) => {
const proj = {
firstName: 1,
lastName: 1,
email: 1,
};
User.findOneAsync({ email }, proj)
.then(user => {
const userData = {
userId: user._id,
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
};
return done(null, userData);
});
})
);

passport.use('signUp', new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true,
}, (req, email, password, done) => {
request({
url: ${API_URL}/signIn,
method: 'POST',
json: {
email,
password,
},
'Content-Type': 'application/json',
Accept: 'application/json',
headers: {
Authorization: token,
},
}, (err, resp, body) => {
const respBody = body || {};
if (err) return done(err);

if (!respBody.email) return done(null, false, { message: respBody.err });

return done(null, { user: respBody });

});
}));
};`

Could you point out in your code where your object is being serialized and are you using a promise? If not might be resolving your issue as your call to wherever you send the object to will resolve before completing the operation with a promise...might resolve this issue if I understand what is the problem correctly.