0
votes

I have a cookie containing a signed jwt that is valid for 5 mins. The jwt contains basic user info (for authentication), as well as a globally unique ID (guid). I store these guids in a database if they are valid, and on the next request after expiration of the jwt, I want to:

1.) Check the database for the guid and see that it is still valid (not blacklisted)

2.) Update the jwt within the cookie with a new 5minute validity and the same information

There are many errors I've run into, but nothing I've tried has worked, and I'm curious as to whether this is even possible or the correct approach at this point.

I am using the node js packages "passport-jwt" in conjunction with "jsonwebtoken" to create the jwts and verify them.

//////////////////////
//authorization.js
//////////////////////

const JWTStrategy = require('passport-jwt').Strategy;
const jwt = require('jsonwebtoken');
const mongoose = require('mongoose');

require('../models/Guids');
const Guids = mongoose.model('Guids');

module.exports.JWTStrategy = function (passport) {
    passport.use('jwt', new JWTStrategy({
        jwtFromRequest: req => cookieExtractor(req, 'token'), 
        secretOrKey: 'secret',
        passReqToCallback: true
    },
        (req, jwt_payload, done) => {
            if (Date.now() / 1000 > jwt_payload.exp) {
                Guids.findOne({ _id: jwt_payload.guid, userId: jwt_payload.uid })
                    .then(guid => {
                    if (guid.valid) {
                            //REFRESH TOKEN HERE
                            //????????return done(null, jwt_payload);
                        } else {
                            //FORCE USER TO RE-AUTHENTICATE
                            //???????return done('access token expired');
                        }
                })
                .catch(err => {
                    console.log(err);
                    return done('failed to validate user');
                });
            } else {
                return done(null, jwt_payload);
            }
        }
    ));
};

var cookieExtractor = function (req, tokenName) {
    var token = null;
    if (req && req.cookies) {
        token = req.cookies[tokenName];
    } else {
        console.log('no cookie found');
    }
    return token;
};

--

/////////////////////////////
//app.js
/////////////////////////////
const express = require('express');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const passport = require('passport');
const jwt = require('jsonwebtoken');

const app = express();

require('./authorization').JWTStrategy(passport);

app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(cookieParser());

//Protected Route
app.get('/xyz', passport.authenticate('jwt', {session: false}),  (req, res) => {
    //route logic....
});

//login Route (creates token)
app.post('/login', (req, res, next) => {
payload = {
    guid: 12345678901010101',
    uid: '123456789',
};

req.login(payload, { session: false }, (err) => {
    if (err) {
        console.log(err);
    } else {
        const token = jwt.sign(payload, 'secret', {expiresIn: '30s'});
        res.cookie('token', token, { httpOnly: true });
        res.redirect('/xyz');
    };
    }
}

const port = process.env.PORT || 5000;
const server = app.listen(port, () => {
    console.log(`Server started on port ${port}`);
});

By default, when the token expires, the authenticate middleware on the protected route immediately throws a failure.

I would like to bypass this failure and instead execute some code in the "authorization.js" file where the comment says "REFRESH TOKEN HERE".

That line of code is never even reached because of the automatic failure! I've tried console logging before and after expiration.

I have even manually bypassed the automatic failure before, but the response object (res) which contains the cookies is not available in the passport-jwt strategy. I am a little lost on where this logic should be implemented if the designated spot is nonsensical due to it being a middleware function.

Additionally, if the protected route is a POST, and the token expired after the page was "GET"ted successfully, I would like to not impede the POST method. I would like to seamlessly refresh the token, and then move along with the POST.

1
I'm not sure if I understood your intention for the usage of a GUID right... Because of what you're using one? If you wan't to send custom authentication messages with passport you could use a custom callback/middleware: function(req, res, next) { passport.authenticate(...)(req, res, next); } See for details in section Custom Callback0x1C1B
@0x1C1B In the example I posted, I hardcoded a single GUID. In my actual code, I am generating a unique Id for each login POST request, and storing them in a database. I'll take a look at the custom callback functionality and get back on this post if it's working. ThankssorsumIntel
@0x1C1B Had to completely overhaul my middleware, but you were right, a custom callback was the correct method. If you make a reply, I'll accept your response as correct. Thank you very much for pointing me towards that documentation!sorsumIntel

1 Answers

0
votes

Passport provides support for custom messages or to be more precise for custom callbacks. You have to call the passport authenticate middleware manually by yourself embedded in your own wrapper middleware. This allows access to the req and res objects. For more details see the documentation.

If the built-in options are not sufficient for handling an authentication request, a custom callback can be provided to allow the application to handle success or failure.

app.get('/login', function(req, res, next) {
  passport.authenticate('local', function(err, user, info) {
    if (err) { return next(err); }
    if (!user) { return res.redirect('/login'); }
    req.logIn(user, function(err) {
      if (err) { return next(err); }
      return res.redirect('/users/' + user.username);
    });
  })(req, res, next);
});