3

I am using express & jwt-simple to handle login/register & authenticated requests as a middleware api. I'm trying to create a .well-known endpoint so other api's can authenticate request based on token send in.

Here's my strategy:

module.exports = function() {
    const opts = {};
    opts.jwtFromRequest = ExtractJwt.fromAuthHeader();
    opts.secretOrKey = securityConfig.jwtSecret;
    passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
        // User.where('id', jwt_payload.id).fetch({withRelated: 'roles'})
        console.log('jwt_payload', jwt_payload)
            User.where('id', jwt_payload.id).fetch()
            .then(user => user ? done(null, user) : done(null, false))
            .catch(err => done(err, false));
    }));
};

Here's my login route:

router.post('/login', function(req, res) {
    const {username, password} = req.body;
    Promise.coroutine(function* () {
        const user = yield User.where('username', username).fetch();

    if(user) {
        const isValidPassword = yield user.validPassword(password);
        if (isValidPassword) {
            let expires = (Date.now() / 1000) + 60 * 30
            let nbf = Date.now() / 1000
            const validatedUser = user.omit('password');

            // TODO: Verify that the encoding is legit..
            // const token = jwt.encode(user.omit('password'), securityConfig.jwtSecret);
            const token = jwt.encode({ nbf: nbf, exp: expires, id: validatedUser.id, orgId: validatedUser.orgId }, securityConfig.jwtSecret)
            res.json({success: true, token: `JWT ${token}`, expires_in: expires});
        } else {
            res.status(401);
            res.json({success: false, msg: 'Authentication failed'});
        }
    } else {
        res.status(401);
        res.json({success: false, msg: 'Authentication failed'});
    }
    })().catch(err => console.log(err));
});

Here's my .well-known route:

router.get('/.well-known', jwtAuth, function(req, res) {
    // TODO: look over res.req.user. Don't seem to be the way to get those parameters.
    // We dont take those parameters from the decrypted JWT, we seem to grab it from the user in DB.
    const { id, orgId } = res.req.user.attributes;
    console.log("DEBUG: userId", id)
    console.log("DEBUG: USER", res.req.user)
    res.json({
        success: true,
        userId: id,
        orgId
    });
});

here's my jwtAuth() function:

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

How would I actually get the token in the route function & decrypt it? All this does right now which works is that it authenticates if true however I need to be able to decrypt the token to send back the stored values. I'm not sure what res.req.user.attributes comes from, is this the token?

SomeRandomDev
  • 129
  • 11
Joelgullander
  • 1,624
  • 2
  • 20
  • 46

1 Answers1

7

Take a look at passport-jwt and in your passport-config (or wherever you initialize passport) setup JWT Strategy:

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

const jwtAuth = (payload, done) => {
 const user = //....find User in DB, fetch roles, additional data or whatever
 // do whatever with decoded payload and call done
 // if everything is OK, call
 done(null, user);
 //whatever you pass back as "user" object will be available in route handler as req.user

 //if your user does not authenticate or anything call
 done(null, false);
}

const apiJwtOptions: any = {};
apiJwtOptions.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
apiJwtOptions.algorithms = [your.jwt.alg];
apiJwtOptions.secretOrKey = your.jwt.secret;
//apiJwtOptions.issuer = ???;
//apiJwtOptions.audience = ???;
passport.use('jwt-api', new JwtStrategy(apiJwtOptions, jwtAuth));

If you want just decoded token, call done(null, payload) in jwtAuth.

Then in your route files when you want to protect endpoints and have info about user, use as:

const router = express.Router();
router.use(passport.authenticate('jwt-api', {session: false}));

And in handler you should have req.user available. It is configurable to what property of req you store data from auth, req.user is just default.

PeS
  • 3,757
  • 3
  • 40
  • 51
  • Hi, sorry for the delay. I noticed I am using passport-jwt for picking up the jwtHeader & extracting / decoding with secret. I use simple-jwt on /login & /register. My issue is that if I login and get back a token to the webclient, and then half a second after try to call an authenticate route based on the returned JWT it doesn't return anything ( no authenticated ). However waiting 2 sec and calling it, it works... Seems like there is a delay when saving the jwt to passport – Joelgullander Aug 20 '18 at 21:00
  • Do you got a few minutes on private chat? – Joelgullander Aug 20 '18 at 21:00
  • I am away, on vacation.. We can chat when I return. – PeS Aug 22 '18 at 22:43
  • Is this still an issue for you or did you resolve that in meantime?? – PeS Sep 05 '18 at 08:43
  • I did not know that when returning user object as second argument in callback, we can get the info later from req.user. Thank you! – Seagull Jun 23 '20 at 09:15