jaredhanson / passport

Simple, unobtrusive authentication for Node.js.
https://www.passportjs.org?utm_source=github&utm_medium=referral&utm_campaign=passport&utm_content=about
MIT License
22.86k stars 1.24k forks source link

passport + webpack doesn't work in production #665

Open maooricio opened 6 years ago

maooricio commented 6 years ago

Hi,

I am using passport to make the login routine of the users of my app. My app is developed in React, and I am using webpack as my bundler. When I compile my app in a development environment all works fine, but when I compile it in a production environment using webpack, it throws me the following error:

/Users/macbook/Documents/lisapp/pos_lisa/node_modules/bluebird/js/release/async.js:61
        fn = function () { throw arg; };
                           ^

TypeError: Cannot read property 'statusText' of undefined
    at /Users/macbook/Documents/lisapp/pos_lisa/built/server/index.js:15:194155
    at Strategy.strategy.error (/Users/macbook/Documents/lisapp/pos_lisa/node_modules/passport/lib/middleware/authenticate.js:340:18)
    at verified (/Users/macbook/Documents/lisapp/pos_lisa/node_modules/passport-local/lib/strategy.js:81:28)
    at /Users/macbook/Documents/lisapp/pos_lisa/built/server/index.js:15:228957
    at tryCatcher (/Users/macbook/Documents/lisapp/pos_lisa/node_modules/bluebird/js/release/util.js:16:23)
    at Promise.errorAdapter [as _rejectionHandler0] (/Users/macbook/Documents/lisapp/pos_lisa/node_modules/bluebird/js/release/nodeify.js:35:34)
    at Promise._settlePromise (/Users/macbook/Documents/lisapp/pos_lisa/node_modules/bluebird/js/release/promise.js:566:21)
    at Promise._settlePromise0 (/Users/macbook/Documents/lisapp/pos_lisa/node_modules/bluebird/js/release/promise.js:614:10)
    at Promise._settlePromises (/Users/macbook/Documents/lisapp/pos_lisa/node_modules/bluebird/js/release/promise.js:689:18)
    at Async._drainQueue (/Users/macbook/Documents/lisapp/pos_lisa/node_modules/bluebird/js/release/async.js:133:16)
    at Async._drainQueues (/Users/macbook/Documents/lisapp/pos_lisa/node_modules/bluebird/js/release/async.js:143:10)
    at Immediate.Async.drainQueues (/Users/macbook/Documents/lisapp/pos_lisa/node_modules/bluebird/js/release/async.js:17:14)
    at runCallback (timers.js:672:20)
    at tryOnImmediate (timers.js:645:5)
    at processImmediate [as _immediateCallback] (timers.js:617:5)

I read a lot about this issue but none of the solutions works with me. What I am missing? What I am doing worng? In advance thanks for anything that help me.

Here is my webpack server configuration

const ExtractTextPlugin = require('extract-text-webpack-plugin');
const path = require('path');
const webpack = require('webpack');
const fs = require('fs');

var nodeModules = {
  "sendmail": "sendmail"
};

fs.readdirSync('node_modules')
  .filter(function(x) {
    return ['.bin'].indexOf(x) === -1;
  })
  .forEach(function(mod) {
    nodeModules[mod] = 'commonjs ' + mod;
  });

const config = {
  entry: ['babel-polyfill', path.join(__dirname, '../source/server.jsx')],
  output: {
    filename: 'index.js',
    path: path.join(__dirname, '../built/server'),
    publicPath: process.env.NODE_ENV === 'production' ? 'https://pos.lisapp.co:3001/' : 'http://localhost:3001/'
  },
  module: {
    rules: [
      {
        test: /\.json$/,
        use: 'json-loader',
      },
      {
        test: /\.jsx?$/,
        exclude: /(node_modules)/,
        loader: 'babel-loader',
        query: {
          presets: ['latest-minimal', 'react'],
          env: {
            production: {
              plugins: ['syntax-async-functions', 'bluebird-async-functions', 'transform-regenerator', 'transform-runtime'],
              presets: ['es2015']
            },
            development: {
              presets: ['latest-minimal']
            }
          }
        },
      },
      {
        test: /\.css$/,
        exclude: /(node_modules)/,
        loader: 'css-loader/locals?modules'
      },
      {
        test: /\.css$/,
        exclude: /(node_modules|source)\/(?!(react-table)\/).*/,
        loader: 'css-loader/locals?modules'
      },
      {
        test: /\.css$/,
        exclude: /(node_modules|source)\/(?!(react-touch-screen-keyboard)\/).*/,
        loader: 'css-loader/locals?modules'
      },
      {
        test: /\.inline.svg$/,
        loaders: [ 'babel-loader',
          {
            loader: 'react-svg-loader',
            query: {
              svgo: {
                plugins: [{removeTitle: false}],
                floatPrecision: 2
              }
            }
          }
        ]
      },
      {
        test: /\.jpe?g$|\.gif$|\.png$|^(?!.*\.inline\.svg$).*\.svg$/,
        loader: 'url-loader?limit=400000'
      },
    ],
  },
  externals: nodeModules,
  target: 'node',
  resolve: {
    extensions: ['.js', '.jsx', '.css', '.json'],
  },
  node: {
    fs: 'empty',
    dns: 'mock',
    net: 'mock',
    child_process: 'empty',
    tls: 'empty'
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        'NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
      }
    }),
    new webpack.optimize.OccurrenceOrderPlugin(true),
    new ExtractTextPlugin({
      filename: '../statics/styles.css',
      ignoreOrder: true,
    })
  ]
  // watch: true,
};

if (process.env.NODE_ENV === 'production') {
  config.plugins.push(
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false,
      },
      mangle: {
        except: ['$super', '$', 'exports', 'require']
      }
    })
  );
}

module.exports = config;

Here is my webpack client configuration

const ExtractTextPlugin = require('extract-text-webpack-plugin');
const path = require('path');
const webpack = require('webpack');

const config = {
  entry: ['babel-polyfill', path.join(__dirname, '../source/client.jsx')],
  output: {
    filename: 'app.js',
    path: path.join(__dirname, '../built/statics'),
    publicPath: process.env.NODE_ENV === 'production' ? 'https://pos.lisapp.co:3001/' : 'http://localhost:3001/'
  },
  module: {
    rules: [
      {
        test: /\.json$/,
        use: 'json-loader',
      },
      {
        test: /\.jsx?$/,
        exclude: /(node_modules)/,
        loader: 'babel-loader',
        query: {
          presets: ['es2016', 'es2017', 'react'],
          plugins: ['transform-es2015-modules-commonjs'],
          env: {
            production: {
              plugins: ['syntax-async-functions', 'bluebird-async-functions', 'transform-regenerator', 'transform-runtime'],
              presets: ['es2015']
            },
            development: {
              plugins: ['transform-es2015-modules-commonjs']
            }
          }
        },
      },
      {
        test: /\.css$/,
        exclude: /(node_modules)/,
        loader: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: 'css-loader?modules',
        }),
      },
      {
        test: /\.css$/,
        exclude: /(node_modules|source)\/(?!(react-table)\/).*/,
        loader: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: 'css-loader',
        }),
      },
      {
        test: /\.css$/,
        exclude: /(node_modules|source)\/(?!(react-touch-screen-keyboard)\/).*/,
        loader: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: 'css-loader',
        }),
      },
      {
        test: /\.inline.svg$/,
        loader: 'babel-loader!react-svg-loader?' + JSON.stringify({
          svgo: {
            // svgo options
            plugins: [{removeTitle: false}],
            floatPrecision: 2
          }
        }),
      },
      {
        test: /\.jpe?g$|\.gif$|\.png$|^(?!.*\.inline\.svg$).*\.svg$/,
        loader: 'url-loader?limit=400000'
      },
    ],
  },
  target: 'web',
  resolve: {
    extensions: ['.js', '.jsx', '.css', '.json'],
  },
  node: {
    fs: 'empty',
    dns: 'mock',
    net: 'mock',
    child_process: 'empty',
    tls: 'empty'
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        'NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
      }
    }),
    new webpack.optimize.OccurrenceOrderPlugin(true),
    new ExtractTextPlugin({
      filename: '../statics/styles.css',
      ignoreOrder: true,
    })
  ]
  // watch: true,
};

if (process.env.NODE_ENV === 'production') {
  config.plugins.push(
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false,
      },
      mangle: {
        except: ['$super', '$', 'exports', 'require']
      }
    })
  );
}

module.exports = config;

And here is the node function where I call the passport strategy to make the authentication:

lisaApp.post('/login', function(req, res, next) {
  const validationResult = validateLoginForm(req.body);
  if (!validationResult.success) {
    return res.status(400).json({
      success: false,
      message: validationResult.message,
      errors: validationResult.errors
    });
  }

  passport.authenticate('local', function(err, user, token) {
    if (err) {
      if (err.response.statusText === 'Unauthorized') {
        return res.status(400).json({
          success: false,
          message: 'fail'
        });
      }

      return res.status(500).json({
        success: false,
        message: 'fail'
      });
    }

    req.logIn(user, function(err) {
      if (err) { console.log(err); }

      return res.json({
        success: true,
        redirectUrl: '/dashboard',
        message: 'success',
        token,
        client: user
      });
    });
  })(req, res, next);
});
maooricio commented 6 years ago

Well, after 1 day of prove all the things, I decided to put some logic, taking into account that the problem comes from the async function that uses 'bluebird' to resolve Promises. The thing here is that in development, the webpack plugins accept the PassportJS connection as Promises, but when I compiled my app to production was mandatory to use async/await to resolve Promises, and PassportJS as itself do not work well with async functions. So we have to do manually. You have to manage the strategy with async/await manually as follows:

exports.localStrategy = new LocalStrategy({
  usernameField: 'userclient',
  passwordField: 'password',
  session: false,
  passReqToCallback: true
}, async (req, userclient, password, done) => {
  var authData = {
    userclient,
    password
  }

  var authToken = await ClientApi.authenticate(authData);

  if(authToken) {
    var token = authToken.data
    var user = await ClientApi.getClient(userclient);

    if (user) {
      user.token = token
      const userData = {
        id: (user.data.client_id) ? user.data.client_id : user.data.clientId,
        userclient: (user.data.userclient) ? user.data.userclient : ((user.data.sellerUserName) ? user.data.sellerUserName : user.data.kitchenUserName),
        codeOfSeller: (user.data.client_id) ? false : ((user.data.codeOfSeller) ? user.data.codeOfSeller : 'kitchenSeller'),
        token: user.token
      };

      done(null, userData, token);
    } else {
      done(err);
    }

  } else {
    done(err);
  }
})

Hope it helps to someone, I really wasted many time, but I learnt. If you want you can declare this issue as closed