14
votes

I'm trying to setup JWT authentication using passport-jwt. I think I've taken the right steps, but a test GET won't succeed and I don't know how to debug it.

Here's what I've done:

  1. setup passport-jwt straight out of the doc as much as possible

    var jwtOptions = {
        secretOrKey: 'secret',
        issuer: "accounts.examplesoft.com",  // wasn't sure what this was, so i left as defaulted in the doc
        audience: "yoursite.net"   // wasn't sure what this was, so i left as defaulted in the doc
      };
    
    jwtOptions.jwtFromRequest = ExtractJwt.fromAuthHeader();
    
    passport.use(new JwtStrategy(jwtOptions, function(jwt_payload, done) {
      User.findOne({id: jwt_payload.sub}, function(err, user) {
        if (err) {
            return done(err, false);
        }
        if (user) {
            done(null, user);
        } else {
            done(null, false);
            // or you could create a new account
        }
      });
    }));
    
  2. Added a token result to my user /login endpoint

    var jwt = require('jsonwebtoken');
    // ...
    
    exports.postLogin = function(req, res, next) {
      passport.authenticate('local', function(err, user, info) {
        if (err) throw err;
        if (!user) {
            return res.send({ msg: 'Login incorrect' });
        }
        req.logIn(user, function(err) {
            if (err) throw err;
            var secretOrKey = jwtOptions.secretOrKey;
            var token = jwt.sign(user, secretOrKey, {
                expiresIn: 631139040 // 20 years in seconds
            });
            res.send({ user: user, jwtToken: "JWT " + token });
        });
      })(req, res, next);
    };
    

Things were looking good up to here. I can login a user (using passport local auth) and the response was a I hoped...

{ "user": { "_id": "56c8b5bd80d16ef41ec705dd", "email": "[email protected]", "password": "$2a$10$zd ... etc.", "__v": 0, }, "jwtToken": "JWT eyJ0eXAiOiJ .... etc." }

I created an unprotected test route like this...

// in my routes file
app.get('/user/tokenTest', user.tokenTest);

And in my controller, a simple endpoint...

exports.tokenTest = function(req, res) {
    console.log(req.headers);
    res.send("token test!!");
};

And GET-ing that works fine, too.

  1. But then I try to protect that route like this:

    app.get('/user/tokenTest', passport.authenticate('jwt', { session: false }),
        user.tokenTest);
    

After I do that, nothing but sadness. I send a request like this:

curl -k 'https://localhost:3443/user/tokenTest' -H 'Authorization: JWT eyJ0eXAiOiJ... etc.' 

And always, always get a 401:

Unauthorized

Console logs in the controller don't seem to execute, neither does logging in the passport.use strategy method. I've tweaked and tweaked, but I'm a little lost. The passport-jwt doc just supplies the example, and virtually no other help.

Please, any ideas about either a mistake that I'm making above, or at least how to go about debugging??

2
What did you do with the app.get route? You say you protected the app.post route, but your curl uses the GET method.bolav
@bolav - good catch. thanks. i mistakenly protected it as a post route. changing it to get (see edit), i'm still left with basically the same problem (see updated error).user1272965
@user1272965 I am working on a similar project, can you tell me how do you store the jwt on client-side ? Because we need it in our header for every call to our API, so how did you store it on the client-side ?Harshit Trehan

2 Answers

27
votes

For any poor soul that follows me here: the passport-jwt doc implies that the auth header should look like this...

Authorization: JWT JSON_WEB_TOKEN_STRING.....

That turned out to be misleading (for me, anyway).

Fortunately, thanks to this article I was able to learn how the token is built. (The token's prefix up to the first '.' is the base64 encoding of the scheme. That "JWT " at the front was noise that prevented the validation from working.

So the fix was to change the token returned by the user controller from:

    res.send({ user: user, jwtToken: "JWT " + token });

To the simpler:

    res.send({ user: user, jwtToken: token });

Phew. Is it me, or is it really a bummer how inadequately these things are explained in so many node package docs??

0
votes

I may be late but I had a similar problem, and I have another solution. You can use this options.jwtFromRequest = ExtractJwt.fromAuthHeaderWithScheme('JWT') to extract the JWT token from authentication header with the following format:

Authorization: JWT JSON_WEB_TOKEN_STRING.....

Here is the documentation I used: https://github.com/themikenicholson/passport-jwt

Extracting the JWT from the request

There are a number of ways the JWT may be included in a request. In order to remain as flexible as possible the JWT is parsed from the request by a user-supplied callback passed in as the jwtFromRequest parameter. This callback, from now on referred to as an extractor, accepts a request object as an argument and returns the encoded JWT string or null. Included extractors

A number of extractor factory functions are provided in passport-jwt.ExtractJwt. These factory functions return a new extractor configured with the given parameters.

fromHeader(header_name) creates a new extractor that looks for the JWT in the given http header
fromBodyField(field_name) creates a new extractor that looks for the JWT in the given body field. You must have a body parser configured in order to use this method.
fromUrlQueryParameter(param_name) creates a new extractor that looks for the JWT in the given URL query parameter.
fromAuthHeaderWithScheme(auth_scheme) creates a new extractor that looks for the JWT in the authorization header, expecting the scheme to match auth_scheme.
fromAuthHeaderAsBearerToken() creates a new extractor that looks for the JWT in the authorization header with the scheme 'bearer'
fromExtractors([array of extractor functions]) creates a new extractor using an array of extractors provided. Each extractor is attempted in order until one returns a token.