Passport JWT works with empty payload - node.js

I have initialized strategy for JWT:
const jwtStrategyOptions = {
jwtFromRequest: ExtractJwt.fromHeader('x-access-token'),
secretOrKey: 'publicKey',
}
passport.use(
new JwtStrategy(
jwtStrategyOptions,
(payload, done) => {
MySQL.Users.readOne(['id'], { id: payload.userId })
.fork(
error => {console.log(error)
done(error)},
user => {
console.log(user)
done(null, user)}
)
}
)
)
And middleware:
const isAuthenticated: RequestHandler = (req, res, next) => {
passport.authenticate(
'jwt',
{ session: false, failWithError: true },
(error, user) => {
//error is null when I pass empty payload
if (error) {
return next(error)
}
req.user = user
return next()
}
)(req, res, next)
}
But when I pass empty or invalid token Passport just pass this
(payload, done) => {
MySQL.Users.readOne(['id'], { id: payload.userId })
.fork(
error => {console.log(error)
done(error)},
user => {
console.log(user)
done(null, user)}
)
}
step and code execute next() function.
Can I somehow detect that payload is invalid or empty?

I'm not quite sure about the MySQL call return type, but if nothing matches the id, does it raise an error?
(payload, done) => {
MySQL.Users.readOne(['id'], { id: payload.userId })
.fork(
error => {console.log(error)
done(error)},
user => {
console.log(user)
done(null, user)}
)
}
If it doesn't raise an error but return null or empty value, you need to check it in the 'success' callback function, because in this case it will call done(null, user) with an empty value.
Based on your comment, this might help, some code that I was using to check for a token expiration error :
passport.authenticate('jwt',
{session: false},
//we need this callback to return information on why it's failing
//err is not populated, but 'info' is...
(err, user, info) => {
if (err) {
return next(err);
}
//if we couldn't authenticate the user, check why
//401 is used when no token or random information is provided
//403 is used when a well-formed token is provided, but it has expired thus not valid anymore
if (!user) {
if (info.name === 'TokenExpiredError') {
return res.status(403).send(info.name);
}
else {
return res.status(401).send(info.message);
}
}
req.user = user;
return next();

Related

Cannot set headers after they are sent to the client Error while regenerating sessions

I'm facing with the "Cannot set headers after they are sent to the client" Error in Express/Passportjs.
I do know that this error mainly occurs when callbacks has called twice, or more;,
but I don't understand why this error has occured. I've specified Error cases on the bottom.
Here's the code that I've wrote in app.js for the strategy setup
passport.use(new KakaoStrategy(
{
clientID: kakao_key,
callbackURL: '/userSchemaAPI/login/kakao/callback',
},
async (accessToken, refreshToken, profile, done) => {
try {
const foundUser = await User.findOne(
{
snsId: profile.id,
provider: 'kakao',
},
);
if (foundUser) {
return done(null, foundUser);
}
else{
return done(null, false, profile);
}
} catch (error) {
return done(error);
}
},
),
);
this is the router code
router.get('/login/kakao', passport.authenticate('kakao'));
router.get('/login/kakao/callback', (req, res, next) => {
passport.authenticate('kakao', function (err, user, info){
if (err) {
return next(err);
}
if (!user) {
const { id } = info;
req.session.joinUser = {
snsId: id,
email: info._json.kakao_account.email,
username: info._json.properties.nickname,
};
return req.session.save(() => {
res.redirect('/userSchemaAPI/register/kakao');
});
}
return req.login(user, function (error){
if (error) {
return next(error);
}
return res.redirect('/');
});
})(req, res, next);
});
I try to register new user when user hasn't registered in our DB. here's the
code when POST request has called in routes '/userSchemaAPI/register/kakao' -
module.exports.createNewKakaoUser = async(req,res,next)=>{
try {
const { snsId, username, email } = req.session.joinUser;
const user = await User.create({
provider : 'kakao',
snsId : snsId,
email: email,
username: req.body.username || username,
});
req.session.regenerate(() => {
req.login(user, (error) => {
if (error) {
return next(error);
}
return res.redirect('/');
});
});
} catch (error) {
console.error(error);
next(error);
}
}
There's 2 major Errors that I am currently facing with.
I can't directly logIn when my info is already in DB.
I succeeded to register my Info in DB, but server makes an "Cannot set headers after they are sent to the client" Error while logging in.
Your code will continue later after next is handled if you don't return
req.session.regenerate(() => {
req.login(user, (error) => {
if (error) {
return next(error);
}
return res.redirect('/');
});
});

Identifyng Error: Cannot set headers after they are sent to the client on my passportJS app

I'm getting that error, and besides asking where it is on my code I would like to know if there is a better way to log errors in order to get to know where it is being generated on my code, since I can't figure it out by reading the error log. Here are the code and the error. Thanks! StackOF is making me add more details, I don't know what to write
/////// app.js
//Function one : setting up the LocalStrategy
passport.use(
new LocalStrategy((username, password, done) => {
User.findOne({ username: username }, (err, user) => {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, { message: "Incorrect username" });
}
if (bcrypt.compare(password, user.password, (err, res) => {
if (res) {
// passwords match! log user in
return done(null, user)
} else {
// passwords do not match!
return done(null, false, { message: "Incorrect password" })
}
})) {
return done(null, false, { message: "Incorrect password" });
}
return done(null, user);
});
})
);
//Functions two and three: Sessions and serialization
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
//Route for logging in
app.post(
"/log-in",
passport.authenticate("local", {
successRedirect: "/",
failureRedirect: "/"
})
);
app.use(function(req, res, next) {
res.locals.currentUser = req.user;
next();
});
app.get("/", (req, res) => {
res.render("index", { user: req.user });
});
app.get("/sign-up", (req, res) => res.render("sign-up-form"));
app.post("/sign-up", (req, res, next) => {
bcrypt.hash(req.body.password, 10, (err, hashedPassword) => {
// if err, do something
if (err) {
return next(err);
}
// otherwise, store hashedPassword in DB
// eslint-disable-next-line no-unused-vars
const user = new User({
username: req.body.username,
password: hashedPassword
}).save(err => {
if (err) {
return next(err);
}
return res.redirect("/");
});
});
});
app.get("/log-out", (req, res) => {
req.logout();
res.redirect("/");
});
app.listen(3000, () => console.log("app listening on port 3000!"));
And the error:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:558:11)
at ServerResponse.header (C:\Users\Atom\Desktop\passportJSOdin\node_modules\express\lib\response.js:771:10)
at ServerResponse.location (C:\Users\Atom\Desktop\passportJSOdin\node_modules\express\lib\response.js:888:15)
at ServerResponse.redirect (C:\Users\Atom\Desktop\passportJSOdin\node_modules\express\lib\response.js:926:18)
at complete (C:\Users\Atom\Desktop\passportJSOdin\node_modules\passport\lib\middleware\authenticate.js:266:26)
at C:\Users\Atom\Desktop\passportJSOdin\node_modules\passport\lib\middleware\authenticate.js:275:15
at pass (C:\Users\Atom\Desktop\passportJSOdin\node_modules\passport\lib\authenticator.js:431:14)
at Authenticator.transformAuthInfo (C:\Users\Atom\Desktop\passportJSOdin\node_modules\passport\lib\authenticator.js:453:5)
at C:\Users\Atom\Desktop\passportJSOdin\node_modules\passport\lib\middleware\authenticate.js:272:22
at C:\Users\Atom\Desktop\passportJSOdin\node_modules\passport\lib\http\request.js:52:7
at C:\Users\Atom\Desktop\passportJSOdin\node_modules\passport\lib\sessionmanager.js:26:5
at pass (C:\Users\Atom\Desktop\passportJSOdin\node_modules\passport\lib\authenticator.js:277:43)
at serialized (C:\Users\Atom\Desktop\passportJSOdin\node_modules\passport\lib\authenticator.js:286:7)
at C:\Users\Atom\Desktop\passportJSOdin\app.js:63:5
at pass (C:\Users\Atom\Desktop\passportJSOdin\node_modules\passport\lib\authenticator.js:294:9)
at Authenticator.serializeUser (C:\Users\Atom\Desktop\passportJSOdin\node_modules\passport\lib\authenticator.js:299:5)
There is a glaring issue in the logic of your code here:
if (bcrypt.compare(password, user.password, (err, res) => {
if (res) {
return done(null, user)
} else {
return done(null, false, { message: "Incorrect password" })
}
})) {
return done(null, false, { message: "Incorrect password" });
}
return done(null, user);
bcrypt.compare() is an asynchronous function, which means it does not return the result it produces to its calling code, but instead passes it to its callback function once available. So the continuation of the code - ie everything that should happen once the result is available - should be wrapped in its callback function.
You got this right. But then you are duplicating the logic of what happens in this callback function in the calling code based on the returned value of bcrypt.compare() - which is irrelevant.
The end result is that the done() function will end up being called twice.
You should just do:
bcrypt.compare(password, user.password, (err, res) => {
if (res) {
return done(null, user)
} else {
return done(null, false, { message: "Incorrect password" })
}
})
As far as logging errors, a stack trace is as good as it gets to help pinpoint the issue.

How to resolve Passport-jwt token unauthorized error?

I am persistently getting 'unauthorized' error while authenticating using a JWT. Below is my controller code:
exports.loginPost = async (req, res) => {
winston.info('Calling loginPost()...');
passport.authenticate('local', { session: false }, (err, user, info) => {
if (err) {
return utils.errorHandler(res, err);
} else if (!user) {
return utils.errorHandler(res, {
statusCode: 403,
message: 'Incorrect username or password.'
});
}
const token = jwt.sign(user, sharedSecret, { expiresIn: '24h' });
//req.user = user;
return res.json({ user, token });
// req.login(user, { session: false }, (err) => {
// if (err) {
// res.send(err);
// }
// // generate a signed json web token with the contents of user object and return it in the response
// const token = jwt.sign(user, sharedSecret, { expiresIn: '24h' });
// //req.user = user;
// return res.json({ user, token });
// });
})(req, res);
};
exports.isUserLoggedIn = async (req, res) => {
let login = {"message": "all good !"}
console.log(req)
return res.status(200).json(login);
//return res.status(200).json(req.user);
};
and passport.js strategy script is as follows:
passport.use(new LocalStrategy({
usernameField: 'username',
passwordField: 'password'
}, async function (username, password, cb) {
//this one is typically a DB call. Assume that the returned user object is pre-formatted and ready for storing in JWT
try {
let user = await userService.getUserWithPassword(username, password);
console.log("passport.js: ",user);
if (!user || user.walletKey !== password) {
throw { statusCode: 403, message: 'Incorrect username or password.' };
}
// // purge password field
// delete user.currentPassword;
return cb(null, user, { message: 'Logged In Successfully' });
} catch (err) {
cb(err);
}
}));
passport.use(new JWTStrategy({
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
secretOrKey: sharedSecret,
passReqToCallback: true
},
async function (req, jwtPayload, cb) {
// Return user object from the JWTPayload
try {
let user = await userService.getUserWithPassword(jwtPayload.walletName, jwtPayload.walletKey);
console.log("passport.js: ",user);
req.user = user
return cb(null, user); //jwtPayload
} catch(err){
return cb(err,false);
}
}
));
I am able to generate token successfully, however, on calling isUserLoggedIn method using Bearer Token, it's prompting me unauthorized error. I am not making an traditional db call to login, instead I am just creating account in a Hyperledger-Indy pool nodes. Using swagger express middleware on a Node.js app.
Adding isUserLoggedIn method script below:
exports.isUserLoggedIn = async (req, res) => {
//let login = {"message": "all good !"}
console.log(req)
return res.status(200).json(req.user);
//return res.status(200).json(req.user);
};

passport local strategy's not getting called

I'm currently making app with vue & express.
I adopted passport as authentication library and I've used it several times.
so I know that when I make login request, passport middleware authenticates user by 'xx strategy'.
The problem is register request works well but login isn't.
I constantly get 'false' for response.data.
I can't even guess where the false comes from.
There is no return statements that returns false.
follow is my code.
passport.deserializeUser((authId, done) => {
let hasUser
let users = User.findOneByAuthId(authId)
.then(results => (hasUser = results.length))
if (hasUser) {
console.log('deserialize!')
done(null, users[0])
} else {
console.log('no user')
done(null, { message: 'no user' })
}
})
passport.use(new LocalStrategy(
(username, password, done) => {
let hasUser
let users = User.findOneByEmail(username)
.then(results => (hasUser = results.length))
if (hasUser) {
let user = users[0]
let { hash } = user
if (User.checkPassword(password, hash)) {
done(null, { message: 'success' })
} else {
done(null, { message: 'wrong password' })
}
} else {
done(null, { message: 'wrong email' })
}
}
))
router.post('/register', (req, res) => {
if (!fun.emailChecker(req.body.username)) {
return res.status(403).json({
message: 'Invalid Email'
})
}
if (!fun.passwordChecker(req.body.password)) {
return res.status(403).json({
message: 'Invalid Password'
})
}
let hasUser
User.findOneByEmail(req.body.username)
.then(results => (hasUser = results.length))
if (hasUser) {
return res.status(409).json({
message: 'Email Exist'
})
} else {
let user = {
authId: 'local: ' + req.body.username,
email: req.body.username,
hash: User.hashPassword(req.body.password),
displayName: req.body.displayName
}
User.create(user)
.then(results => {
if (!results) {
throw new Error('user creation error')
} else {
req.login(user, err => {
if (!err) {
req.session.save(() => {
return res.json({ success: true })
})
}
})
}
})
}
})
router.post('/login', (req, res) => {
passport.authenticate('local', (err, result) => {
if (!err) {
return res.json(result)
}
})(req, res)
})
// vue component
methods: {
onSubmit () {
axios.post('http://localhost:3001/auth/login', {
email: this.email,
password: this.password
}).then(response => console.log(response.data))
},
There are various issues with your code.
Starting with incorrect promise handling:
let hasUser
let users = User.findOneByAuthId(authId)
.then(results => (hasUser = results.length))
if (hasUser) { ... }
You are trying to make asynchronous code synchronous here. The code that depends on the result of the asynchronous query has to be moved to inside the then handler (both in deserializeUser and the strategy verification handler):
User.findOneByAuthId(authId).then(users => {
let hasUser = users.length;
if (hasUser) {
console.log('deserialize!')
done(null, users[0]);
} else {
console.log('no user')
done(Error('no user'));
}
});
(I'm not quite sure why your method findOneByAuthId, whose name implies that there will be at most one result, could result in an array)
Also, you're not using the correct convention for passing user data and login errors back to Passport:
if (User.checkPassword(password, hash)) {
done(null, { message: 'success' })
} else {
done(null, { message: 'wrong password' })
}
This should look like this (and obviously, other places where done is called incorrectly should be fixed too):
if (User.checkPassword(password, hash)) {
done(null, user);
} else {
done(null, false, { message: 'wrong password' })
}
(documented here under "Verify Callback")
Finally, you're using passport.authenticate() incorrectly, which is probably the cause of the false being returned. Instead, try this:
router.post('/login', passport.authenticate('local'), (req, res) => {
return res.json(req.user); // or whatever you want to return in case of login success
})
(documented here; if you want to use a custom callback, look for "Custom Callback" on that page)

why my koa-passport authenticate parameter user is always undefined?

I'm trying to use koa-passport for koa2, and followed the examples of the author, but i always get "Unauthorized". I used the console.log and found that it even not hit the serializeUser.
var UserLogin = async (ctx, next) =>{
return passport.authenticate('local', function(err, user, info, status) {
if (user === false) {
ctx.body = { success: false }
} else {
ctx.body = { success: true }
return ctx.login(user)
}
})(ctx, next);
};
And then I searched on the web and found another writing of router, it goes to the serializeUser but the done(null, user.id) threw error that "cannot get id from undefined".
let middleware = passport.authenticate('local', async(user, info) => {
if (user === false) {
ctx.status = 401;
} else {
await ctx.login(ctx.user, function(err){
console.log("Error:\n- " + err);
})
ctx.body = { user: user }
}
});
await middleware.call(this, ctx, next)
The auth.js are showed below. Also I followed koa-passport example from the author here and tried to use session, but every request i sent will get a TypeError said "Cannot read property 'message' of undefined". But I think this is not the core problem of authentication, but for reference if that really is.
const passport = require('koa-passport')
const fetchUser = (() => {
const user = { id: 1, username: 'name', password: 'pass', isAdmin: 'false' };
return async function() {
return user
}
})()
const LocalStrategy = require('passport-local').Strategy
passport.use(new LocalStrategy(function(username, password, done) {
fetchUser()
.then(user => {
if (username === user.username && password === user.password) {
done(null, user)
} else {
done(null, false)
}
})
.catch(err => done(err))
}))
passport.serializeUser(function(user, done) {
done(null, user.id)
})
passport.deserializeUser(async function(id, done) {
try {
const user = await fetchUser();
done(null, user)
} catch(err) {
done(err)
}
})
module.exports = passport;
By the way when I use the simple default one, it will just give me a "Not found". But through console.log I can see it actually got into the loginPass.
var loginPass = async (ctx, next) =>{
passport.authenticate('local', {
successRedirect: '/myApp',
failureRedirect: '/'
});
};
In server.js:
// Sessions
const convert = require('koa-convert');
const session = require('koa-generic-session');
app.keys = ['mySecret'];
app.use(convert(session()));
// authentication
passport = require('./auth');
app.use(passport.initialize());
app.use(passport.session());
Thanks a lot for any help!!! :D

Resources