3
votes

I have authentication working with passport JWT, but I am having difficulty running a callback function/response, only if a user is authenticated. How can this be done?

In my routes:

import express from 'express';
import passport from '../../config/passport';
import memberInfoCtrl from '../controllers/memberInfo';

const router = express.Router();

router.route('/members')
  .get(membersCtrl.tokenCheck, passport.authenticate('jwt', { session: false }), membersCtrl.doSomethingElse);

export default router;

I want membersCtrl.doSomethingElse to be run if authentication is successful.

Here is my tokenCheck function:

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

      if (!user) {
        return res.status(403).send({
          success: false, msg: 'Authentication failed. User not found.'
        });
      }
      // res.json({ success: true, msg: 'Welcome to the member area!' });
      return next();
    });
  } else {
    return res.status(403).send({ success: false, msg: 'No token provided.' });
  }
}

Using res.json in tokenCheck works fine, but the doSomethingElse function is not getting called afterwards.

I thought it's because we send a response like this:

res.json({ success: true, msg: 'Welcome to the member area!' });

Replacing the res.json with return next(); returns the error:

Error: Unknown authentication strategy \"jwt\"

Why does this happen - how can I check for authentication before executing another function, for the same route?

I'm sure it's something silly i'm missing. Any help is appreciated!

Here is part of my main express script that initialises passport:

import passport from 'passport';
...
app.use(passport.initialize());

app.use('/api', routes);
...

Passport config:

const JwtStrategy = require('passport-jwt').Strategy;
import User from '../server/models/user';
import config from './env';

module.exports = (passport) => {
  const opts = {};
  opts.secretOrKey = config.secret;
  passport.use(new JwtStrategy(opts, (jwtPayload, done) => {
    User.findOne({ id: jwtPayload.id }, (err, user) => {
      if (err) {
        return done(err, false);
      }
      if (user) {
        done(null, user);
      } else {
        done(null, false);
      }
    });
  }));
};
1
No-one able to help? I am getting a headache :-)Tony Barnes

1 Answers

5
votes

Your general approach is correct. To protect a route depending on some conditions write a custom middleware calling next() if those conditions are fulfilled.

But in your case this is not necessary as this is what passport-jwt does. So assuming that you configured passport and passport-jwt correctly all you need to write is this:

router.route('/members')
    .get(passport.authenticate('jwt', { session: false }), membersCtrl.doSomethingElse);

passport-jwt will extract the JWT from the request and verify it against your provided secret or key. Afterwards it will use passport's verify callback to populate req.user (source).

Additionally: Yes after using res.json() a response is sent which is why your passport middleware and anything beyond is not reached in that case.

Regarding your error Error: Unknown authentication strategy \"jwt\": This usually happens if you did not configure your passport authentication correctly. If you include it in your question I will take a look at it and extend my answer accordingly.

Update: Your code looks good to me except one thing: You did not specify the jwtFromRequest attribute in your passport-jwt options which is mandatory. Or did you by any chance forget to invoke your passport config?

Update 2: Further clarification regarding my comment below:
1.) Import your ES6 passport config module (where you added the jwtFromRequest option) in your main express script and invoke it:

import passport from 'passport';
import passportConfig from 'path/to/passport/config';
...
app.use(passport.initialize());
passportConfig(passport);
...

2.) Make sure to remove your tokenCheck function, you don't need it. See the first part of this answer.

Update 3: 'Unauthorized' is great because it means that you are successfully protecting your /members route now. To implement a token-based authentication you now need an additional route /authenticate where users can request access to your web service e.g. by providing credentials (your route will verify these credentials and respond with a JWT which must be signed with the same secret you are using for passport-jwt - otherwise passport-jwt will not be able to verify the token signature later).

But this goes beyond the scope of this question. There should be many resources out there covering this. You can for example implement an /authenticate route using jsonwebtoken which is shown in this article (they are not using passport at all but use jsonwebtoken for token creation and validation).

Note: If you are also implementing a client you have to include some additional logic there to store the JWT and to include it in your requests to the express app.