node.jsexpressjwtpassport.jspassport-jwt

Passport authenticate jwt not responding


I have a basic MERN login/signup app with a dummy protected route to be visible only to logged in users. I have set up everything related to login and sign up. I am sending the jwt token from server in a cookie. The aprt up to this works fine. I can sign up and I can login. But when I make a GET call to /protected route, passport.jwt doesn't seem to work. Nothing happens in there. Here is my passport.js config:

import {Strategy} from 'passport-jwt';
// import {ExtractJwt} from 'passport-jwt';
import passport from 'passport';
import mongoose from 'mongoose';
import {UserSchema} from '../models/userModel';
import {config} from './config';

const User = mongoose.model('User', UserSchema);

const cookieExtractor = (req) => {
    let token = null;
    if(req && req.cookies) {
        token = req.cookies['jwt'];
    }
    return token;
}

export const passportConfig = (passport) => {
    const opts = {};
    opts.jwtFromRequest = cookieExtractor;
    opts.secretOrKey = config.key;
    passport.use(
        new Strategy(opts, (jwt_payload, done) => {
            User.findById(jwt_payload.id)
                .then((user) => {
                    if(user) {
                        return done(null, user);
                    }
                    return done(null, false);
                })
                .catch((err) => {
                    console.log('Error in finding user by id in jwt.');
                });
        })
    );
};

Here is the userRoutes.js:

import { addNewUser, loginUser, verifyLogin } from '../controllers/userController';

export const routes = (app) => {
    app.route('/users/register')
        .post(addNewUser);
    app.route('/users/login')
        .post(loginUser);
    app.route('/users/protected')
        .get(verifyLogin);
}

Finally, here are the verifyLogin and loginUser controllers in my userController.js:

export const loginUser = (req, res) => {
    console.log(req.body);
    if(mongoSanitize.has(req.body)) {
        return res.send(400).json({error: "Characters $ and . are prohibited. Use different values."});
    }

    const {errors, isValid} = validateLoginInput(req.body);
    if(!isValid) {
        return res.status(400).json(errors);
    }

    const userName = req.body.userName;
    const password = req.body.password;

    User.findOne({userName: userName})
        .then((user) => {
            if(!user) {
                return res.status(404).json({userNotFound: "Username not found."});
            }
            else {
                bcrypt.compare(password, user.password)
                    .then((isMatch) => {
                        if(isMatch) {
                            const payload = {
                                id: user.id,
                                name: user.fullName
                            };
                            const token = jwt.sign(payload, config.key, {expiresIn: 60});
                            res.cookie('jwt', token, {httpOnly: true, secure: false});
                            res.status(200).json({success: true, token: token});
                        }
                        else {
                            return res.status(400).json({incorrectPassword: "Password incorrect."});
                        }
                    })
            }
        });
};

export const verifyLogin = () => {
    console.log('protected'); //'protected' is printed
    passport.authenticate('jwt', {session: false}),
    (req, res) => {
        console.log('here'); //'here' is not printed
        const { user } = req;
        res.send(200).send({user});
    };
};

The controller loginUser works just fine, I can see the token in response and also I have a cookie with the jwt key:value pair. But the 3rd controller verifyLogin doesn't do anything after passport.authenticate call.

Any ideas where I might be doing something wrong ?


Solution

  • Okay so I was able to fix. Anyone else having the same problem, the issue was that I wasn't using passport.authenticate as a middleware, instead I was calling it as a function with a callback on successful authentication. Here are the change that I made:

    userRoutes.js:

    export const routes = (app) => {
        app.route('/register')
            .post(addNewUser);
        app.route('/login')
            .post(loginUser);
        app.route('/protected')
            .get(passport.authenticate('jwt', {session:false}), verifyLogin);
    }
    

    And then verifyLogin just sends the logged in user information back:

    export const verifyLogin = (req, res) => {
        console.log('here');
        const { user } = req;
        res.status(200).json({message:'Secure route', user: user});
    };
    

    Everything else is pretty much the same.