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 ?
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.