1

I have been following the tutorial on creating a test API application from this article. At the end of the article i see a mention its best to encrypt the jwt token for added security so i wen searching for a way to do that as well. I ran into this article and it gives examples of how to encrypt the jwt token with RSA private/public keys.

THIS is where im getting stuck. After i have successfully signed up using the /signup route, i can then use the /login route to get my token. So im assuming this is where i use my private key to encrypt the token before sending it back to the user?

**Made repo public for testing - you will need only to provide a mongoDB connection string in app.js

Im stuck at the encrypt/decrypt portion of the process, any help appreciated.

router.post("/login", async (req, res, next) => {
  passport.authenticate("token", async (err, user, info) => {
    try {
      if (err || !user) {
        const error = new Error("An Error occurred");
        return next(error);
      }
      req.login(user, { session: false }, async error => {
        if (error) return next(error);
        //We don't want to store the sensitive information such as the
        //user password in the token so we pick only the email and id
        const body = { _id: user._id, email: user.email };
        //Sign the JWT token and populate the payload with the user email and id
        const token = jwt.sign({ user: body }, PRIV_KEY, { algorithm: 'RS256' });
        //Send back the token to the user
        return res.json({ token });
      });
    } catch (error) {
      return next(error);
    }
  })(req, res, next);
});

And then when making calls to the "secure" routes i need to decrypt the token and verify it against the public key?

router.get("/profile", (req, res, next) => {
  //We'll just send back the user details and the token

  jwt.verify(req.query.token, PUB_KEY, { algorithms: ['RS256'] }, function(err, decoded) {
    if (err.name === "TokenExpiredError") {
      console.log("Whoops, your token has expired!");
    }

    if (err.name === "JsonWebTokenError") {
      console.log("That JWT is malformed!", err); <------ GET ERROR HERE
    }

    if (err === null) {
      console.log("Your JWT was successfully validated!");
    }

    // Both should be the same
    console.log(decoded);
    res.json({
      message: "You made it to the secure route",
      user: req.user
    });
  });
});
user616
  • 783
  • 4
  • 18
  • 44

1 Answers1

2

I don’t have the time to reproduce this. Your login part seems correct. However, you should try to setup protected routes like this, copied and tailored to your needs from your first article:

Setting up middleware to handle jwt decryption, make sure to require it in your app.js or wherever you need to, if you set it up in a separate file. This can be used as a middleware later on in your controllers:

const JWTstrategy = require('passport-jwt').Strategy;
//We use this to extract the JWT sent by the user
const ExtractJWT = require('passport-jwt').ExtractJwt;

//This verifies that the token sent by the user is valid
passport.use(new JWTstrategy({
  //secret we used to sign our JWT
  secretOrKey : PUB_KEY,
  algorithms: ['HS256'],
  //we expect the user to send the token as a query parameter with the name 'token'
  jwtFromRequest : ExtractJWT.fromUrlQueryParameter('token')
}, async (token, done) => {
  try {
    //Pass the user details to the next middleware
    return done(null, token.user);
  } catch (error) {
    done(error);
  }
}));

Setup protected route, note that you don’t need to manually call jwt.verify, middleware handles it and populates req.user:

const express = require('express');

const router = express.Router();

//Let's say the route below is very sensitive and we want only authorized users to have access

//Displays information tailored according to the logged in user
router.get('/profile', passport.authenticate('jwt', { session: false }), (req, res, next) => {
  //We'll just send back the user details and the token
  res.json({
    message : 'You made it to the secure route',
    user : req.user,
    token : req.query.token
  })
});

module.exports = router;

**Update based on your comment:

I cloned your repo and it is working for me, although I changed some things:

  • I added app.use(bodyParser.json()); to app.js so that I could send the request bodies as json - this is not necessary if you prefer urlencoded

  • the problem is that secureRoute that you export is another router and you try to use it as a controller in app.js:

...
const secureRoute = require('./routes/secure-routes');
...
app.use('/user', passport.authenticate('jwt', { session: false }), secureRoute);`

*note that it will be /user route, if you want /profile please change it in like app.use('/profile', ...)

so instead of

router.get("/profile", (req, res, next) => {
  //We'll just send back the user details and the token
  res.json({
    message: "You made it to the secure route",
    user: req.user,
    token: req.query.secret_token
  });
});

it should be just a controller function:

...
module.exports = (req, res, next) => {
  //We'll just send back the user details and the token
  res.json({
    message: 'You made it to the secure route',
    user: req.user,
    token: req.query.token // note that you don't use secret_token but token as a name
  });
};
  • the third thing is to not forget to add token to the query params, when you call the API, like http://localhost:3000/user?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7Il9pZCI6IjVlODc2Yjc1YTVlNTk3MTRlOGFjMmI4NyIsImVtYWlsIjoiYUBiLmNvbSJ9LCJpYXQiOjE1ODU5MzMyNjR9.lcLuQeCMRy7Ef9zNkIt_rn4S22t2cm7YLRE7Jgp1Mpw

you should get the response:

{
    "message": "You made it to the secure route",
    "user": {
        "_id": "5e876b75a5e59714e8ac2b87",
        "email": "a@b.com"
    }
}
f4z3k4s
  • 966
  • 7
  • 13
  • So when i try that, once i get the token and then apply that to the /profile route i get a 401 Unauthorized response back from Express. – user616 Apr 03 '20 at 15:38
  • Also i added the public repo to my github project. Just need to add your own mongoDB connection string to app.js – user616 Apr 03 '20 at 15:47
  • Just quickly reproduced based on your repo. Please check the edited answer. – f4z3k4s Apr 03 '20 at 17:15
  • So after making the suggested changes in both your comments im still getting 401 errors when requesting the /profile route with the token. I have checked in all the changes to github repo – user616 Apr 03 '20 at 17:52
  • I forked your repo: https://github.com/f4z3k4s/PassportTest . Please check, works. So basically you still had some inconsistencies. You used publicKey for decrypting but used a string for encrypting. The other thing is that you should go to /user?token=xy not /profile?token=xy. The third thing is that you need to encrypt and decrypt with the private key. Why? Because your key files are incorrect for decrypting with public key. For more information, please see: https://stackoverflow.com/questions/55012194/asymmetric-keys-with-passport-jwt-verify-always-returns-unauthorized – f4z3k4s Apr 03 '20 at 18:41
  • Ok, yes that did work!. So what is the use case of the public key? – user616 Apr 03 '20 at 18:53
  • Asymmetric decrypting is more secure than symmetric decrypting. RSA is asymmetric since you have 2 keys. A private to decrypt and public to encrypt. In your code now, we use it in a symmetric fashion because your key files are broken probably. Basically we use your private key file now if it was a normal string. I don't exactly know the problem with your key files, but if you feel the spirit in yourself, you can give it a try to figure it out. Maybe it is not a problem with your key files, I don't feel like further investigating. But good luck, hope I answered your question. – f4z3k4s Apr 03 '20 at 19:21