1

I'm currently facing the following error, I'm developing an API in node js for let's say a virtual shop. An user, for getting its items in cart needs to be authenticated. The authentication is being handled via JWT, and im using passport module for that propose.

For the routes that require authentication, when the API is consumed using the 'Authentication' header with its correspondent 'JWT ey...' token, the server is not handling any request. Just responds to the OPTIONS preflight request and nothing more. I've tried consuming the /api/cart from an angular front end using httpClient, and the authorization header and getting the following error in the chrome devTools

chrome console log

As well i've tried using postman, sending the authorization header and couldn't get any response screen appears.

I'm logging the OPTIONS request headers. The following is the server response after hitting /api/cart with authorization header and it stops there.

 !OPTIONS
    { host: 'localhost:3000',
      connection: 'keep-alive',
      'access-control-request-method': 'GET',
      origin: 'http://localhost:4200',
      'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36',
      'access-control-request-headers': 'authorization,content-type',
      accept: '*/*',
      'accept-encoding': 'gzip, deflate, br',
      'accept-language': 'en-US,en;q=0.9' }
    !OPTIONS
    OPTIONS /api/cart 200 1.288 ms - -

server.js

    const mongoose = require("mongoose");
    const express = require("express");
    const bodyParser = require("body-parser");
    const morgan = require("morgan");
    const passport = require("passport");
    const config = require("./config/database"); //Getting databas config file
    const User = require("./app/models/user"); //we're getting mongoose model
    const Product = require("./app/models/product");

    const app = express();
    const port = process.env.PORT || 3000;
    const routes = require("./app/routes/index");

    mongoose.connect(config.database);

    //bodyParser to get our request/response parameters
    app.use(
      bodyParser.urlencoded({
        extended: false
      })
    );
    app.use(bodyParser.json({ limit: "50mb" }));

    //log request to console
    app.use(morgan("dev"));
    app.use(passport.initialize());

    //pass passport for connfiguration
    require("./config/passport")(passport);

    //allow cors
    app.use(function(req, res, next) {
      res.setHeader("Access-Control-Allow-Origin", "*");
      res.setHeader(
        "Access-Control-Allow-Headers",
        "Origin, X-Requested-With, Content-Type, Accept, Authorization"
      );
      res.setHeader(
        "Access-Control-Allow-Methods",
        "GET, POST, OPTIONS, PUT, PATCH, DELETE"
      );
      if (req.method === "OPTIONS") {
        console.log("!OPTIONS");
        res.end();
      }
      next();
    });

    routes(app);

    app.listen(port);
    console.log("express app started on port " + port);

routes/index.js

const productApiRouter = require("./product.routes");
const userApiRouter = require("./user.routes");

module.exports = (app) => {
  app.use("/api", userApiRouter); //routes for users
  app.use("/products", productApiRouter); // routes for products
};

routes/user.routes

require('../models/user')
const express = require('express');
const passport = require('passport')

const isAuthenticated = require('../controllers/auth.controller')

const router = express.Router();

var userController = require('../controllers/user.controller');


router.get('/', userController.getUser)
router.get('/cart', passport.authenticate('jwt', {
    session: false,
    failWithError: true
  }),  userController.getCart)
router.post('/deletecart/:id', userController.deleteCartById)
router.post('/authenticate', userController.authenticate)
router.post('/signup', userController.signupUser)
router.get('/verify_email', userController.verifyEmailByUrl)
router.post('/addcart/:id', userController.addItemToCart)
router.post('/update_user', userController.updateUser)

module.exports = router;

controllers/user.controller.js get cart method

exports.getCart = (req, res) => {
  var token = getToken(req.headers);
  if (token) {
    var decoded = jwt.decode(token, config.secret);
    User.findOne(
      {
        email: decoded.email
      },
      function(err, user) {
        if (err) throw err;

        if (!user) {
          return res.status(404).send({
            success: false,
            message: "Not user found"
          });
        } else {
          var cart = user.itemsInCart;
          console.log(cart);
          var items = addItemCount.addItemCount(cart);
          console.log(items);
          res.status(200).send(JSON.stringify(items));
        }
      }
    );
  } else {
    console.log('Request ')
     res.status(403).send({
      success: false,
      message: "Unauthorized request"
    });
  }
};

config/passport ---> passport configuration

const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const User = require('../app/models/user');
const config = require('./database');

//add a JWT strategy to our passport
module.exports = function(passport) {
    var opts = {};
    opts.jwtFromRequest = ExtractJwt.fromAuthHeaderWithScheme("jwt");
    opts.secretOrKey = config.secret;
    passport.use('jwt', new JwtStrategy(opts, function(jwt_payload, done) {
        User.findOne({
            id: jwt_payload.id //try to find a user given jwt_payload.id
        }, function(err, user) {
            if (err) {
                return done(err, false);
            }
            if (user) {
                done(null, user);
            } else {
                done(null, false)
            }
        });
    }));
}

getToken function

 var getToken = function(headers) {
     if (headers && headers.authorization) {
         var parted = headers.authorization.split(' ');
         if (parted.length === 2) {
             return parted[1];
         } else {
             return null;
         }
     } else {
         return null;
     }
 };

 module.exports = getToken
  • In getCart, where is `jwt` declared? It is used as `jwt.decode(token, config.secret);`. Also, can you post code of `getToken`? – Vasan Feb 20 '18 at 01:36
  • @Vasan jwt = require("jwt-simple"). I just added the get token function. Thanks in advance – Juan Camilo Giraldo Chaverra Feb 20 '18 at 14:57
  • Your code looks fine at high level. I think the only way to resolve this would be to debug through to see if it is executing the code as expected (for eg, if it enters the middleware for getCart, if it gets the token etc). There are only 2 possibilities I can think of - mongo getting stuck for findOne or a middleware stopping the request i.e. neither calling `next` nor finishing response. – Vasan Feb 20 '18 at 18:49
  • Thanks @Vasan, another thing worth to mention is that all the request that doesn't require preflight request works flawlessly. The only way that the server is not responding is where 'Authorization' headers are added which triggers a preflight request. I'm Still diggin' about this issue, so strange tho – Juan Camilo Giraldo Chaverra Feb 20 '18 at 19:27

1 Answers1

3

After struggling the problem I found the reason the server is not working for request with authorization token.

For a NodeJS server, the header's max size for http requests is 80KB. ref: http_parser.h nodejs source code

#define HTTP_MAX_HEADER_SIZE (80*1024)

In the mentioned requests in the question, I'm attaching some Base64 encoded images to the JWT Token making it way more bigger than the header size permitted by the NodeJS server for http requests.

So the solution is to make sure that the headers size for an http request is below 80K threshold.

  • Very interesting. I'd have expected a 413 or other 4xx error in that scenario (as with other servers). Bug in NodeJS perhaps? – Vasan Feb 20 '18 at 21:04
  • Seems like, gonna put an issue on the node project. Maybe they can bring a more specific response and a reason for the issue I was getting. @Vasan – Juan Camilo Giraldo Chaverra Feb 21 '18 at 16:16
  • To make nodejs server to respond to this kind of requests, you can make it send one by attaching a `server.on('clientError')` listener. Newer versions of node.js do that automatically but v6.x doesn't. – Juan Camilo Giraldo Chaverra Feb 21 '18 at 18:37