Node-jsonwebtoken: "expiresIn" option not working with sequelize object

Created on 9 Dec 2015  路  21Comments  路  Source: auth0/node-jsonwebtoken

The token is created but never expires. I am getting the object from a query to a relational db with the sequelize package. Code example:

models.User.findOne({ where: {username: req.body.name} }) //Returns an object user or null
.then(function(user) {
    if (user) {
        console.log(typeof user); //Logs "object"
        // Check if password matches
        if (user.password != req.body.pass) {
            res.json({ message: 'Authentication failed. Wrong password.' });
        } else {
            // If user is found and password is right create a token
            var token = jwt.sign(
                user, 
                'shhhhh', 
                {
                    expiresIn: 60 // expires in 1 minute
                }
            );

            // Shows the token
            console.log(token);
        }   
    } else {
        console.log("User not found");
    }

}).catch(function(error){
    whatever...
}); 

Most helpful comment

I am using a similar method that works for me, except that I only use only the username as token:

    User.findOne({username: username}, (err, user) => {
      if (err) {
        return done(err, false);
      }
      if (!user) {
        return done(null, false, {message: 'Invalid credentials'});
      }
      bcrypt.compare(password, user.password, (err, isMatching) => {
        if (err) {
          return done(err, false);
        }
        if (!isMatching) {
          return done(null, false, {message: 'Invalid credentials'});
        }

        var token = jwt.sign({user: user.username}, config.secrets.jwt, {
          expiresIn: '10m',
          algorithm: 'HS256'
        });
        user.token = token;
        User.update({_id: user._id}, {token: user.token, reauth: false}, () => {
          user = _.pick(user, ['username', 'token']);
          return done(null, user);
        });
      });
    });

What infos does decoding your token give on http://jwt.io/ ?

All 21 comments

I am using a similar method that works for me, except that I only use only the username as token:

    User.findOne({username: username}, (err, user) => {
      if (err) {
        return done(err, false);
      }
      if (!user) {
        return done(null, false, {message: 'Invalid credentials'});
      }
      bcrypt.compare(password, user.password, (err, isMatching) => {
        if (err) {
          return done(err, false);
        }
        if (!isMatching) {
          return done(null, false, {message: 'Invalid credentials'});
        }

        var token = jwt.sign({user: user.username}, config.secrets.jwt, {
          expiresIn: '10m',
          algorithm: 'HS256'
        });
        user.token = token;
        User.update({_id: user._id}, {token: user.token, reauth: false}, () => {
          user = _.pick(user, ['username', 'token']);
          return done(null, user);
        });
      });
    });

What infos does decoding your token give on http://jwt.io/ ?

I'm using a similar way to sign the token and am occurring the same problem at the moment. Everything works fine, however the token doesn't seem to expire. I'm storing the token in the localstorage of the browser.

Didn't update for some time, will try it in the next weeks again

@MariusRumpf I got "Invalid signature" although I am using HS256.
In the details I get "Decoded edit the payload and secret (only HS256 supported)"

I must be doing something wrong because even with dummy data it does not pass the validation of jwt.io:

models.User.findOne({ where: {username: req.body.name} }) //Returns an object user or null
    .then(function(user) {
        if (user) {
        console.log(typeof user); //Logs "object"
        // Check if password matches
        if (user.password != req.body.pass) {
            res.json({ message: 'Authentication failed. Wrong password.' });
        } else {
            // If user is found and password is right create a token
            var profile = {
                first_name: 'John',
                last_name: 'Doe',
                email: '[email protected]',
                id: 123
            };
            //I make sure it is a json object
            var token = jwt.sign(
                profile, 
                'shhhhh', 
                {
                    algorithm: 'HS256',
                    expiresIn: 60 // expires in 1 minute
                }
                );

            // Shows the token
            console.log(token);
        }   
    } else {
        console.log("User not found");
    }

    }).catch(function(error){
        whatever...
    }); 

@gugol2 I think, i have the same problem.

But i have a workaround:
jwt.sign({user: user}, app.get('superSecret')...

It's not the best way, but is it a way.

@pjanfred that did not solve anything in my case.

Here is the result I get from jwt.io

@gugol2 Can you tell me what debugger you use? I will test it with my token.

@pjanfred thats https://jwt.io

@gugol2 In your screenshot I can see that the token doesn't have expiration. what version of jsonwebtoken are you using? You might be using an older version that doesn't support the expiresIn parameter, it used to be called expiresInSeconds

@jfromaniello Thank you :)

@gugol2 At the moment i don麓t have further ideas. Can you show us the part of your sourcecode?

@gugol2 Tested my posted example again, it works correct for me. The tokens decode as expected on https://jwt.io

I, too, ran into this issue upon getting back an object from MongoDB's model I created. I passed the object itself directly on to jwt.sign() as a payload.

Debugging within the jwt.sign() function pointed out that it did call payload.exp = ... setting. However, this did _not_ affect the object.

The solution for me was to create a new object and pass it on as payload. This might be due to ES2015's const?

In either case, I'd propose that the payload could be a new object with parameters and the original payload that's passed on to the function is encapsulated in this new object, like so:

var payload = {
     exp: expirationTimestamp,
     data: originalPayload
};

This would prevent any properties from the original payload from being overwritten.

I had this problem as well, it is because Mongoose and other libraries extend the stringify/toJSON methods of those model objects. (I dont remember how)

In case of mongoose, its enough for you to call modelObject.toJSON() and it solves the issue. Just tested here.

I think this library should add some sort of mechanism to verify this, otherwise, lots of developers may be implementing JWT with expirationIn properly, but without knowing it doesnt work. This is basically critical security issue. Obviously this is developer`s fault in the end, but ... maybe its good to help everyone out.

@jfromaniello from my packaje.json:

"jsonwebtoken": "~5.4.1"

Using version "jsonwebtoken": "^5.5.0" and getting error:

Unhandled rejection Error: "expiresIn" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60

regardless of option use.

var token = jwt.sign(user.email, 'superSecret', { expiresIn: '1d' }); // Expire in 24 nours

I think I found the cause of the problems reported here. This library was trying to modify the input payload directly which was a bad design, this might broke when the payload has been sealed with Object.seal or Object.freeze.

Please try with version: 5.5.2 and let me know.

I had this problem too. Switching back to 5.4.1 corrected it.

After all the help provided here I kind of found the issue. I apologize for my delayed answers and my lack of thoroughness along the procedure.

  • @MariusRumpf first example worked ok but I didn't know why nor how to use http://jwt.io properly at that moment.
  • The workaround of @pjanfred was also correct, just like @MariusRumpf passing an object straight away to jwt.
  • @lucastschmidt hit the nail though, "_Mongoose and other libraries extend the stringify/toJSON methods of those model objects_".

So my conclusions are (using version: "jsonwebtoken": "~5.4.1") to get your token to expire you can do any of these two things:

  • Create and fill the object yourself and pass it to jwt (Sequelize or Mongoose objects may not be in JSON notation).
  • Call modelObject.toJSON() on your object before passing it to jwt.

IMHO probably the first option is better because you can pass just the minimum info in your token.

Thanks to everybody.
Cheers!

@gugol2 for mongoose documents you should use jwt.sign(doc.toObject(), {..});

notice the toObject() call.

Correct @gugol2 , you shouldnt pass the plain mongoose object, since that object may contain properties like hashed password or some other private stuff that shouldnt be available on a token (that can be freely read by anyone).

I solved this by doing the following. If your data is a string such as encrypting your payload before jwt tokenizing.
var options = {
'expiresIn': '1h'
};
var payload = { encTokenData: encStringTokenData };
var token = jwt.sign(payload, 'yoursecret', options);

use dataValues, not just Obj.

ex:
const token = jwt.sign(user.dataValues, env.SECRET, {
expiresIn: 604800 // 1 week
});

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mathellsmelo picture mathellsmelo  路  3Comments

AndreOneti picture AndreOneti  路  3Comments

rockchalkwushock picture rockchalkwushock  路  4Comments

BarukhOr picture BarukhOr  路  4Comments

prevostc picture prevostc  路  4Comments