Payload error in jsonwebtoken

Viewed 22945

I am making a web application using nodejs and angular cli I'm using JWT to authenticate my login function . But when I process it threw this error

Error: Expected "payload" to be a plain object. at validate (D:\Mean_Projects\meanauthapp\node_modules\jsonwebtoken\sign.js:34:11) at validatePayload (D:\Mean_Projects\meanauthapp\node_modules\jsonwebtoken\sign.js:56:10) at Object.module.exports [as sign] (D:\Mean_Projects\meanauthapp\node_modules\jsonwebtoken\sign.js:108:7) at User.comparePassword (D:\Mean_Projects\meanauthapp\routes\users.js:86:27) at bcrypt.compare (D:\Mean_Projects\meanauthapp\models\user.js:53:9) at D:\Mean_Projects\meanauthapp\node_modules\bcryptjs\dist\bcrypt.js:297:21 at D:\Mean_Projects\meanauthapp\node_modules\bcryptjs\dist\bcrypt.js:1353:21 at Immediate.next [as _onImmediate] (D:\Mean_Projects\meanauthapp\node_modules\bcryptjs\dist\bcrypt.js:1233:21) at runCallback (timers.js:785:20) at tryOnImmediate (timers.js:747:5) at processImmediate [as _immediateCallback] (timers.js:718:5)

Here my passport code

    const JwtStrategy= require('passport-jwt').Strategy;
    const ExtractJwt=require('passport-jwt').ExtractJwt;
    const User= require('../models/user');
    const config=require('../config/database');        
    module.exports=function(passport){
    let opts={};
    opts.jwtFromRequest=ExtractJwt.fromAuthHeader();
    opts.secretOrKey=config.secret;
    opts.issuer = 'accounts.examplesoft.com';
    opts.audience = 'yoursite.net';
    passport.use(new JwtStrategy(opts,(jwt_payload,done)=>{
        console.log(jwt_payload);
        User.getUserById(jwt_payload._doc._id,(err,user)=>{
            if(err){
                return done(err,false);
            }
            if(user){
                return done(null,user);
            }
            else{
                return done(null,false);
            }
        });
    }));
}

My code for authenticate and get profile

// Authenticate
router.post('/authenticate', (req, res, next) => {
  const username = req.body.username;
  const password = req.body.password;

  User.getUserByUsername(username, (err, user) => {
    if(err) throw err;
    if(!user){
      return res.json({success: false, msg: 'User not found'});
    }

    User.comparePassword(password, user.password, (err, isMatch) => {
      if(err) throw err;
      if(isMatch){
        const token = jwt.sign(user, config.secret, {
          expiresIn: 604800 // 1 week
        });

        res.json({
          success: true,
          token: 'JWT '+token,
          user: {
            id: user._id,
            name: user.name,
            username: user.username,
            email: user.email
          }
        });
      } else {
        return res.json({success: false, msg: 'Wrong password'});
      }
    });
  });
});

// Profile
router.get('/profile', passport.authenticate('jwt', {session:false}), (req, res, next) => {
  res.json({user: req.user});
});
7 Answers

It fails at the line

const token = jwt.sign(user, config.secret, {

With error "Expected "payload" to be a plain object"

Your user object is initialized here:

User.getUserByUsername(username, (err, user)

Which I assume is mongoosejs object, which contains many methods and is not "serializable". You could handle this by passing a plain object, by either using .lean() from mongoose or plain toJSON method:

const token = jwt.sign(user.toJSON(), config.secret, {
  expiresIn: 604800 // 1 week
});

I had this problem as well, with a returned user from mongoose, just add toJSON() or toObject() will fix the issue, but what happens if your user is not always coming from mongoose?

You will get a

user.toJson/user.ToObject is not a function

if you try to do this on a plain object.

If your user is coming from different sources and you don't know if it will be a plain object or not, you can solve it like this:

JSON.parse(JSON.stringify(user));

It's very simple, if the user comes from database (mongo) then simply do user.toJSON(), if the user comes from any other source then simply do JSON.stringify(user).

if it's not comming from moongose then use spread operator

const token = jwt.sign({ ...payload }, config.secret, {
  expiresIn: 100080
});

Change

const token = jwt.sign(user, config.secret, { expiresIn: 10080 });

To

const token = jwt.sign(user.toJSON(), config.secret, { expiresIn: 10080 });
const token = jwt.sign(user, config.secret, {
      expiresIn: 604800 // 1 week
    });

convert this to

const token = jwt.sign(user.toJSON(), config.secret, {
      expiresIn: 604800 // 1 week
    });

or you need to console.log(jwt_payload); to find your ID comes under _doc or directly with jwt_payload. because this may change with the versions.

Related