mikenicholson / passport-jwt

Passport authentication using JSON Web Tokens
MIT License
1.96k stars 213 forks source link

Always returning 401 Unauthorized with a JWT Token using RS256 #117

Closed mmchougule closed 5 years ago

mmchougule commented 7 years ago

I am following this tutorial to enable jwt authentication in my express API. https://jonathanmh.com/express-passport-json-web-token-jwt-authentication-beginners/

If I use a standard username/password authentication, I am able to use JwtStrategy to authenticate the JWT Token that I receive in the request header. jwt.sign() happens on the user id and secret. All of this works fine.

When I try to modify this code to verify the id_token (JWT Token signed using RS256) from OpenID Connect, then I get 401 Unauthorized no matter what. I tried to debug in the JwtStrategy method and it looks like the request doesn't even go inside that function. This id_token appears to be a lot longer than the one signed with HS256 algorithm.

A simple passport.authenticate call app.get('/callback', passport.authenticate('jwt', { session: false }), function(req, res, next) { });

Can someone please explain why it doesn't even recognize my token?

flouc001 commented 7 years ago

Probably a silly question but what <type> do you have set in your Authorization header? Your header should look like:

Authorization: JWT <token>

mmchougule commented 7 years ago

My header values look like this: authorization:jwt eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IkpSY080bnhzNWpnYzhZZE43STJoTE80...........

screen shot 2017-06-13 at 9 01 43 am
flouc001 commented 7 years ago

@mmchougule It may not be the same situation but my Authorization header type was case sensitive. You are receiving lower case, I am having to use upper case, therefore my request looks like this:

Authorization: JWT <token>

AlexQianjin commented 7 years ago

Today I tried to use passport-jwt(version: 2.2.1), passport(version:0.1.3) and jsonwebtoken(version:7.4.1) to config the jwt authorization. When I sent the GET request, the response was always Unauthorized. I followed all the instructions and spent about two hours on this problem, it wasn't resolved. So I tried another jwt passport middleware called passport-http-jwt-bearer, it works fine. I don't know the reason. Maybe it has some compatibility issues with the latest packages. Here is my demo

linonetwo commented 7 years ago

Me too, parse from body is fine.

mikenicholson commented 7 years ago

@AlexQianjin can you provide a succinct demo that demonstrates the issue - something thatI could just curl a single endpoint and debug?

AlexQianjin commented 7 years ago

@themikenicholson I forgot how I met that problem. So I tried to reproduce it following the tutorial link mentioned by @mmchougule. It works fine and here is my succinct demo. Thanks for your reply!

mmchougule commented 7 years ago

If I follow the code from that tutorial then it works fine. We are generating JWT token ourselves with the username and password in that. My case is that I have the RS256 signed JWT token from an OpenID Connect provider and when I send it over to my express code, the JwtStrategy code doesn't get triggered in this case. I just wrote similar middleware code without using the passport strategy and it works now.

haidang666 commented 6 years ago

i face with the same issue, the callback for jwt not reach. It's look like it can not read the header. Here is the code:

const jwtOptions = {
    jwtFromRequest: ExtractJwt.fromHeader('authorization'),
    secretOrKey: process.env.APP_KEY || DEFAULT_SECRECT
}

const 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(jwtLogin);
passport.use(localLogin);

and in route:

var express = require('express'),
    passport = require('passport');

require('../config/passport');

var user_controller = require('../controllers/userController');

const authMiddware = passport.authenticate('jwt', { session: false });

const router = express.Router();
router.use(authMiddware);

router.get('/users', function(req, res, next) {
    console.log(req.get('Authorization'));
    res.send('huh?');
});

module.exports = router;

here my full code https://github.com/HaiDang666/express2017

daniel-parreira-prodigy commented 6 years ago

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.

https://stackoverflow.com/questions/45897044/passport-jwt-401-unauthorized

soumodips commented 6 years ago

Hello everyone. I still have this issue. Tried using bearer strategy too. Here is the link to my repo https://github.com/soumodips/JWT-passport-OAuth

I will be glad if any one can help.

Screenshots from Postman: image

image

Thanks a ton in advance!

ISSUE SOLVED: Be sure to use all key names as expected by the modules. I had used 'exp' for expiry time in the payload instead of 'expiresIn'. Enjoy!!

heijmerikx commented 6 years ago

Ok, if you follow the tutorial to the letter there are some things changed in the meantime it seems.

What I needed to do in order to get my token accepted on my local machine.

Used the following extractor in the options object:

ExtractJwt.fromAuthHeaderAsBearerToken()

And used the following Authentication header contents;

bearer eyJhbGciOiJIUzI1Ni-restofthetoken...

But the most important part was to not use the following options given in the example:

opts.issuer = 'accounts.examplesoft.com';
opts.audience = 'yoursite.net';

And to be exact, I'm currently using:

passport@0.3.2
passport-jwt@3.0.1
akshaydotsh commented 6 years ago

Hello, I am having the same issue with my authentication. Here is my configuration :

passport.js

    const JwtStrategy = require('passport-jwt').Strategy
    const ExtractJwt = require('passport-jwt').ExtractJwt

    const User = require('../models/user')

    module.exports = function (passport) {
        var opts = {}
        opts.jwtFromRequest = ExtractJwt.fromAuthHeaderWithScheme('JWT')
        opts.secretOrKey = 'secret'

    passport.use(new JwtStrategy(opts, (jwt_payload, done) => {

        User.getUserById(jwt_payload.sub, (err, user) => {

            if (err) {
                return done(err, false)
            }

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

        })

    }))

    passport.serializeUser(function (user, done) {
        done(null, user.id);
    })

}

login request generating the token :

router.post('/login', (req, res) => {
    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({ data: user }, 'secret', {
                    expiresIn: 604800 // 1 week
                })

                res.json({
                    success: true,
                    token: 'JWT ' + token,
                    user: {
                        id: user._id,
                        name: user.name,
                        username: user.username,
                        email: user.email
                    }
                })
            }
            else {
                res.json({
                    success: false,
                    msg: 'Wrong Password'
                })
            }
        })
    })
})

So when I try to fetch a user's profile by the generated token through :

router.get('/profile', passport.authenticate('jwt', { session: false }), (req, res) => {
    res.json({
        user: req.user
    })
})

It always shows Unauthorized

I'm currently using :

   "passport": "^0.4.0",
   "passport-jwt": "^3.0.1"

Can anyone please tell me what I'm missing ?

morozRed commented 6 years ago

Hi everyone, make sure that you guys are using the same secret to .sign() and .verify() token

eric-personal commented 6 years ago

Having the same issue as theakshaygupta. This happens when tokens have expired.

router.get('/profile', passport.authenticate('jwt', { session: false }), (req, res) => {
    res.json({
        user: req.user
    })
})

passport.authenticate does not pass off the error. If I do this.

passport.authenticate('jwt', { session: false }, (error, user, info) => {
  console.log('error = ', error, ' info = ', info, '  user = ', user);
});

Then info shows the expired error message. The question is why does it not pass off this to my first method ?

joshdenz commented 6 years ago

@theakshaygupta

Are you sure that jwt_payload.sub holds the data you think it does?

You sign your token with the payload {data: user}, so to my eyes it looks like you should be looking for jwt_payload.data.user._id in your passport.js file and not jwt_payload.sub.

marsch commented 6 years ago

just as a note to everyone facing that problem. For me it was a missmatch in the parameter naming. Reading the code of the jsonwebtoken package was helping me:

verify.js

...
if (options.issuer) {
    var invalid_issuer =
        (typeof options.issuer === 'string' && payload.iss !== options.issuer) ||
        (Array.isArray(options.issuer) && options.issuer.indexOf(payload.iss) === -1);

    if (invalid_issuer) {
      return done(new JsonWebTokenError('jwt issuer invalid. expected: ' + options.issuer));
    }
  }
...

there you can see that the jwt-payload itself needs to have the iss key not the issuer key. Same with the aud vs. audience key.

so changing it to iss and aud during. the jwt-creating was solving it for me on the verification.

henrybarnacle commented 6 years ago

@theakshaygupta I'm having this same issue, did you find a solution?!

arnasledev commented 6 years ago

+1 the same issue as @theakshaygupta and still looking for answer

akshaydotsh commented 6 years ago

@arnasledev @henrybarnacle

As mentioned above, you've got to ensure that your token is not expired If you want further insight on what could be the possible issue please refer to issue #153

:)

arnasledev commented 6 years ago

@theakshaygupta yeah thats what I though before about expired token but its all good and valid for one month. I just kept stuck on the same problem as this guy mentioned on this comment and it has nothing to do with my token expire time.

ezy commented 6 years ago

My issue was indeed not setting an expiry on the token when signing it.

const token = jwt.sign(user, config.secrets.jwt, { expiresIn: 86400 * 7 });

Here's the relevant parts of code for anyone who requires it in future:

// auth/controller.js
const jwt = require('jsonwebtoken');
const passport = require('passport');
const config = require('../config/config');

function verifyUser(req, res /*, next*/) {
  passport.authenticate('local', { session: false }, (err, user, info) => {
    if (err || !user) {
      return res.status(400).json({
        message: info.message
      });
    }
    req.login(user, { session: false }, (error) => {
      if (error) {
        res.send(error);
      }
      // generate a signed son web token with the contents of user object and return it in the response
      const token = jwt.sign(user, config.secrets.jwt, { expiresIn: 86400 * 30 }); // Expiry!!!!
      // Use to ensure token is valid and debug non-working bearer
      // jwt.verify(token, config.secrets.jwt, (errs, data) => {
      //   console.log(errs, data);
      // });
      res.json({
        user: {
          'firstName': user.firstName,
          'lastName': user.lastName,
          'email': user.email
        },
        message: info.message,
        token
      });
    });
  })(req, res);
}

module.exports = {
  verifyUser
};
// user/route.js
const router = require('express').Router();
const controller = require('./controller');
const passport = require('passport');

router.route('/:id')
  .get(passport.authenticate('jwt', { session: false }), controller.getUser);

module.exports = router;
// auth/passport.js - only showing JWTStrategy
passport.use(new JWTStrategy({
    jwtFromRequest: ExtractJWT.fromAuthHeaderWithScheme('Bearer'),
    secretOrKey: config.secrets.jwt
  },
  (jwtPayload, cb) => {
    // Use the JWT token to find the user in the db if required
    return User.findOne({ where: { email: jwtPayload.email }, raw: true })
      .then((user) => {
        return cb(null, user);
      })
      .catch((err) => {
        return cb(err);
      });
  }
));
AnojaMadusanka commented 6 years ago

use the token as JWT asndbcsdgfqmnsdbswfv..... like that...give a space between JWT and other characters.

Vizzyy commented 6 years ago

https://github.com/themikenicholson/passport-jwt/issues/117#issuecomment-369544889

This did it for me. I did not manually set the headers, I had to go to the Authorization tab of Postman, and select Bearer token, and in the input field for the token i had to REMOVE the "JWT" prepended on there by the generator function. The token would not work with JWT at the beginning.

Tetz commented 6 years ago

Using lower case is simple solution. Express converts Http Header paprameters name to lower case. So try like this

passport.use(new JWTstrategy({
  secretOrKey: 'top_secret',
  jwtFromRequest: ExtractJWT.fromHeader('YOUR-HEADER-PARAM'.toLowerCase())
}, async (token, done) => {
  try {
    return done(null, token.user)
  } catch (error) {
    done(error)
  }
}))
lablancas commented 5 years ago

watch out if you are using a variable from process.env for expiresIn ... make sure you convert to Number when defining you sign your token

aodr3w commented 5 years ago

what worked for me was selecting token bearer in the authorization section of post man , before sending the get request. just like @Vizzyy

prabhatmishra33 commented 5 years ago

Token is not getting expired. Please help!!

// route middleware to verify a token app.use('/api/*',function(req, res, next) {

// check header or url parameters or post parameters for token console.log("Middleware"); var token = req.body.token || req.query.token || req.headers['x-access-token'];

// decode token if (token) {

// verifies secret and checks exp
jwt.verify(token, app.get('superSecret'), function(err, decoded) {      
  if (err) {
    console.log("Token Error!!!!!");
    return res.json({ success: false, message: 'Failed to authenticate token.' });    
  } else {
    // if everything is good, save to request for use in other routes
    req.decoded = decoded;    
    next();
  }
});

} else {

// if there is no token
// return an error
console.log("Token Error!!!!!");
return res.status(403).send({ 
    success: false, 
    message: 'No token provided.' 
});

}

//Token Creation part:

  const payload = {
    user: req.user["username"] ,
    iat:Date.now()
  };
  var token = jwt.sign(payload, 'iLoveMyLaptop', {
    expiresIn : 60  
     //token gets expired in 60 seconds. 
   }); 
mikenicholson commented 5 years ago

Closing as this has become a general "I need help" thread, primarily with a tutorial I didn't author or review and it likely not up to date with the current API of this module.

If you have a specific issue with the module, please provide a succinct and self-contained code example to illustrate the issue - A failing unit test is preferred.

AlexLomm commented 5 years ago

For me, replacing the ExtractJWT.fromAuthHeaderAsBearerToken() with ExtractJWT.fromHeader('authorization') worked.

4sagar commented 5 years ago

in Version 0.4.0, Authorization header for jwt is changed from 'JWT ' + [some token] to 'bearer ' + [some token]

rajat1saxena commented 5 years ago

As of version 4.0.0, this is what you need to do:

  1. Do not use the following fields from the tutorial. Just comment them out or delete them.

    issuer: <>,
    audience: <>
  2. Your JWT strategy options should look like the following:

    const opts = {
    jwtFromRequest: extractJwt.fromAuthHeaderAsBearerToken(),
    secretOrKey: jwtSecret
    }
  3. In Postman, under "Authorization" tab, select "Bearer Token" and populated the Token field with your token (without any JWT or Bearer prefixes)

Babitabisht commented 5 years ago

I wasted more than half an hour, in finding the solution, finally Replacing 'JWT' with 'Bearer' in token key of res.json worked for me. res.json({ success: true, token: "Bearer " + token, .............. hopefully people will save their time by reading this comment.

Ashenou commented 4 years ago

As of version 4.0.0, this is what you need to do:

  1. Do not use the following fields from the tutorial. Just comment them out or delete them.
issuer: <>,
audience: <>
  1. Your JWT strategy options should look like the following:
const opts = {
    jwtFromRequest: extractJwt.fromAuthHeaderAsBearerToken(),
    secretOrKey: jwtSecret
}
  1. In Postman, under "Authorization" tab, select "Bearer Token" and populated the Token field with your token (without any JWT or Bearer prefixes)

3 really helped a lot thanks for sharing. I was sending the token in postman using header - Authorization field with prefix bearer

lucasnzd commented 4 years ago

Make sure the value you are using on the jwt.sign() is a number or if you are using process.env parse it to a number. 💩

blomi commented 4 years ago

I had 401 error for last several hours. For those, who are successfully extracting jwt token but still are getting the 401 error, most likely the problem is in options object. Here's how my code looks like.

passport.use( new JWTStrategy( { ...opts, //<-------MAKE SURE TO DECONSTRUCT THE OPTIONS OBJECT WHICH YOU USED TO GENERATE TOKEN. for me, that was the problem. jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(), secretOrKey: "secret_key" // the private key, used in your algorithm to generate token. }, function(jwtPayload, done) { done(null, jwtPayload); } ) );

Also

  1. make sure to set Authorization header to Bearer ${token}.
  2. here's my opts object: opts = { issuer: "app_name", audience: "app_domain.com", expiresIn: "12h", algorithm: "RS256" };
eyoeldefare commented 4 years ago

Ok, if you follow the tutorial to the letter there are some things changed in the meantime it seems.

What I needed to do in order to get my token accepted on my local machine.

Used the following extractor in the options object:

ExtractJwt.fromAuthHeaderAsBearerToken()

And used the following Authentication header contents;

bearer eyJhbGciOiJIUzI1Ni-restofthetoken...

But the most important part was to not use the following options given in the example:

opts.issuer = 'accounts.examplesoft.com';
opts.audience = 'yoursite.net';

And to be exact, I'm currently using:

passport@0.3.2
passport-jwt@3.0.1

Yeap, I figured all of that too late unfortunately for me. I wish I found your answer before all this work.

nickdotht commented 4 years ago

watch out if you are using a variable from process.env for expiresIn ... make sure you convert to Number when defining you sign your token

Thanks a lot @lablancas ! That was my issue 😄

jerzeemayana commented 4 years ago

goto the postman authorization section and select the "Bearer Token" paste your token in the input filed and also remove the prepend "Bearer || JWT" from the token

passport-jwt@4.0.0 passport@0.4.0

Hope this Helps

njoye commented 4 years ago

Ok, if you follow the tutorial to the letter there are some things changed in the meantime it seems.

What I needed to do in order to get my token accepted on my local machine.

Used the following extractor in the options object:

ExtractJwt.fromAuthHeaderAsBearerToken()

And used the following Authentication header contents;

bearer eyJhbGciOiJIUzI1Ni-restofthetoken...

But the most important part was to not use the following options given in the example:

opts.issuer = 'accounts.examplesoft.com';
opts.audience = 'yoursite.net';

And to be exact, I'm currently using:

passport@0.3.2
passport-jwt@3.0.1

Removing the opts.issuer and opts.audience keys worked wonders - thank you!

vedantnd111 commented 4 years ago

I do not want to use postman to pass token in auth header ,how can i do it , i am getting unauthorized when as output, please answer below is the link to my question. https://stackoverflow.com/questions/63102730/i-am-having-trouble-passing-jwt-token-in-post-authentication-routes

blchelle commented 4 years ago

I was consistently getting the 401 Error "Unauthorized" when sending my requests to protected routes in Postman. I remembered to set a Bearer token followed by my JWT as an HTTP header "Bearer ".

Eventually, I figured out that the problems I was having were caused by my token being expired, so be sure to double-check that your tokens aren't expired by pasting your token into jwt.io.

These were the opts that I used.

const opts: JwtStrategyOptions = {
    jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
    secretOrKey: keys.jwt.secret,
};
Drakan21 commented 4 years ago

Just wanted to leave this here for anyone still having issues with this: (Aug 14 - 2020)

IF you are using Postman to test (which I was) follow the steps along with the { data : user } fixes mentioned above:

1) Go to "Authorization" and select "Inherit Auth from parent" 2) Go to "Header" and manually type into a "key" field "Authorization" (it will be one of the auto-complete options also). Then in the "Value" field - post your key (jwt prepended and all) as is.

The problem is that "Bearer" token preset in Postman Authentication selection automatically prepends your key with "Bearer" as it is meant to be a "template" option. You're meant to add your own "token" manually as described above.

Hope this helps someone.

pabloducato commented 3 years ago

I just want to thank you, there is no solution here, but you pointed me in the right direction and I fixed the problem. Thank you and best regards. ;-)

AkshayShenvi commented 3 years ago

After days of trying to solve this, I found out that my public key was wrong. fixed that and that solved my problem.

iaverypadberg commented 3 years ago

I am still having issues with this. Everything is set up properly, Authorization header, ExtractJwt.fromAuthHeaderAsBearerToken(), and Expiry times are converted to numbers. I can manage to make GET requests work, but POST requests refuse to work on routes requiring authorization. However, in POSTMAN, POST requests work just fine with the same Token that was declined in the previously mentioned POST requests from my client side app. Help!

leonardopaiva commented 3 years ago

Hello, I'm just commenting because it can help someone, I was having trouble authenticating, because I was using the wrong token... in my case, when I log in, I get a token object with 3 different tokens, 1 of them the idToken": { "jwtToken", another refreshToken": { "token": and another accessToken": { "jwtToken, I believe I was using accessToken when I should have been using idToken, hope it helps someone, or even myself in the future

kolinezia commented 2 years ago

Hello, it might be helpful.

It helped me to manually create the files and copy the generated keys there.

Because in the public key, when outputting to the console for the test, the first characters were displayed "��-----BEGIN RSA PUBLIC KEY-----" which I could not remove in the code.

.replace('��','') and other things didn't work

Krishnavir21 commented 2 years ago

Is there any way by which my payload should not be visible when i will be putting my token into jwt.io

nickdotht commented 2 years ago

Is there any way by which my payload should not be visible when i will be putting my token into jwt.io

Nope. It's all readable. If you want encrypted payload, look into PASETO. If not, just make sure to not include any sensitive info in there.