2
votes

I'm trying to implement passport-jwt authentication but I'm always getting 401 Unauthorized when trying to call the endpoint.

Here is my setup

passport.js

var passport = require('passport');
var User = require('../models/user');
var config = require('./auth');
var JwtStrategy = require('passport-jwt').Strategy;
var ExtractJwt = require('passport-jwt').ExtractJwt;
var LocalStrategy = require('passport-local').Strategy;

var localOptions = {
    usernameField: 'email'
};

var localLogin = new LocalStrategy(localOptions, function(email, password, done) {

    User.findOne({
        email: email
    }, function(err, user) {
        if (err) {
            return done(err);
        }
        if (!user) {
            return done(null, false, { error: 'Login failed. Please try again' });
        }

        user.comparePassword(password, function(err, isMatch) {
            if (err) {
                return done(err);
            }
            if (!isMatch) {
                return done(null, false, { error: 'Login Failed. Please try again.' });
            }

            user.status = 'online';
            user.save(function(err, user) {
                if (err) {
                    return done(err);
                }
            });

            return done(null, user);
        });
    });
});

var jwtOptions = {
    jwtFromRequest: ExtractJwt.fromHeader('Authorization'),
    secretOrKey: config.secret
};

var jwtLogin = new JwtStrategy(jwtOptions, function(payload, done) {
    console.log(payload);
    User.findById(payload._id, function(err, user) {
        if (err) {
            return done(err, false);
        }
        if (user) {
            done(null, user)
        } else {
            done(null, false);
        }
    });
});

passport.use(localLogin);
passport.use(jwtLogin);

module.exports = {
    initialize: () => passport.initialize(),
    authenticateJWT: passport.authenticate('jwt', { session: false }),
    authenticateCredentials: passport.authenticate('local', { session: false }),
};

user.js

var express = require('express');
var router = express.Router();
var AuthController = require('../controllers/authentication');
var passportService = require('../config/passport');
var passport = require('passport');

const requireToken = passportService.authenticateJWT;
const requireCredentials = passportService.authenticateCredentials;


router.post('/signup', AuthController.register);
router.post('/signin', requireCredentials, AuthController.login);

router.get('/protected', requireToken function(req, res, next){
res.send({msg:'Success!'});
});

module.exports = router;

I've made sure that my header contains: 'JWT ' + [some token]... Also tried without the 'JWT ' still nothing...

I've checked the other posts about the same problem but still can't resolve it.

3
are you sure thats the needed header? usually the header is called Authorization and the value Bearer mytokenJohannes Merz
According to the docs npmjs.com/package/passport-jwt this seems to be one of the many ways to provide the token. I've tried excracting it from the body but still no results. Also in these posts : stackoverflow.com/questions/43091021/… stackoverflow.com/questions/35528377/… people seem to be doing it the same way.Borislav Popnikolov

3 Answers

8
votes

SHORT
Legacy
'JWT ' + [some token]

Version 0.4.0
'bearer ' + [some token]

EXAMPLE
So when you send the tokens now is like this:
Legacy
res.json ({ success: true, token: 'JWT ' + token })

Version 0.4.0
res.json ({ success: true, token: 'bearer ' + token })

In depth
There may be other ways to do this as well
If you look in the /node_module/passport-jwt/lib/extract_jwt.js file you can see that theres a function called versionOneCompatibility(options)

2
votes

After many hours of trying finally I've managed to fix the problem by using the ExtractJwt.fromAuthHeaderWithScheme('Bearer') method. For some reason the extractor wasn't able to get the token with the other methods.

1
votes

I've got the same issue and after researching i figured out what causing this problem. When you creating you res.json inside the route

//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.toJSON(), config.secret, {
          expiresIn: 604800 // 1 week
        });
        res.json({
          success: true,
          token: 'jwt ' + token, //Here you have to put space after name inside quotes
          user: {
            id: user._id,
            name: user.name,
            username: user.username,
            email: user.email
          }
        });
      } else {
        return res.json({
          success: false,
          msg: 'Wrong password'
        });
      }
    });
  });
});

inside the token:'jwt ' + token,it's really important to put a space behind the name jwt,and also the name you put inside ' ' quotation mark that name you have to put inside your

var opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderWithScheme('jwt');//Here you have to put the same name inside quotes '' like you put inside token but without space after name
opts.secretOrKey = config.secret;
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
    console.log(jwt_payload);
    User.findOne({
        id: jwt_payload.sub
    }, function(err, user) {
        if (err) {
            return done(err, false);
        }

        if (user) {
            return done(null, user);
        } else {
            return done(null, false);
        }
    });
}));

These two have to match,it doesn't matter if you put jwt or JWT or bearer or anything else,the important thing is that they have to match,and there has to be space when you are creating token:'name '.