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)
Related
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('/');
});
});
Hello guys I have a problem trying to get my user session with passport on reactjs. I have no problem to get on post man , I cannot fetch the data with a Get method on react js, im getting undefined:(.
I configurated my cors and everything and still no data :(.
This is my fetch
const [user, setUser] = useState(null);
useEffect(() => {
const getUser = async () => {
try {
const response = await axios.get("http://localhost:8080/login/success");
const data = await response.json();
setUser(data);
} catch (error) {
throw new Error(`error fetching data ${error}`);
}
};
getUser();
}, []);
and this is my end point and passport js config.
function checkAuthentication(req, res, next) {
if (req.isAuthenticated()) next();
else {
res.status(401).json({
message: "Failure",
});
}
}
router.get("/login/success", checkAuthentication, (req, res) => {
if (req.user) {
res.status(200).json({
success: true,
message: "success user",
user: req.user,
});
console.log(req.user);
} else {
req.status(404).json({
success: false,
message: "No user",
})
}
});
passport.use(
"login",
new LocalStrategy(async (username, password, done) => {
try {
const user = await User.findOne({ username: username });
if (!user) {
return done(null, false, { message: "Incorrect username" });
}
const isMatch = await user.isValidPassword(password);
if (!isMatch) {
return done(null, false, { message: "Incorrect password" });
} else {
return done(null, user, { message: "Logged in successfully" });
}
} catch (error) {
console.log(error);
}
})
);
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();
I've implemented passport local strategy using async/await as below
const strategy = new LocalStrategy(
async(username, password, done) => {
try {
// Find the user given the username
const user = await User.findOne({ username });
// If not, send info
if (!user) {
return done(null, false, {
success: false,
message: 'User not found'
})
}
// Check if the password is correct
const isMatch = await user.isValidPassword(password);
// If not, send info
if (!isMatch) {
return done(null, false, {
success: false,
message: 'Invalid Password'
});
}
// Otherwise, return the user
done(null, user);
} catch (error) {
done(error, false);
}
}
);
passport.use(strategy);
And implemented custom callback in routes using the code below.
router.post('/login', async(req, res, next) => {
const { receivedUser, information } = await passport.authenticate('local');
// If a user is found
if (receivedUser) {
res.status(200).json({
success: true,
message: 'Authentication Successful'
});
} else {
// If user is not found
res.status(401).json(information);
}
};
);
There are errors in above custom callback implementation as receivedUser and information are 'undefined'. How to make changes to above custom callback using async/await to remove errors ?
Referred docs:
http://passportjs.org/docs/configure
http://passportjs.org/docs/username-password
I'm starting to use async/await. Generally, what is a pattern to use await with middleware done/next functions?
For example, how could I replace .then() in the code below with await? localAuthenticate is done/next middleware. Do I need to make a separate async function to use await inside it?
I'd like something like this (even better w/o the try/catch):
function localAuthenticate(User, email, password, hostname, done) {
try { // where is async?
// Find user
let user = await User.findOne({ email: email.toLowerCase() }).exec()
if(!user) return done(null, false, { message: 'This email is not registered.' });
// Test password
user.authenticate(password, function(authError, authenticated) {
if(authError) return done(authError);
if(!authenticated) return done(null, false, { message: 'This password is not correct.' });
return done(null, user);
});
} catch(err) { done(err); }
}
Original code from Passport.js authentication middleware:
function localAuthenticate(User, email, password, hostname, done) {
User.findOne({
email: email.toLowerCase()
}).exec()
.then(user => {
if(!user) {
return done(null, false, {
message: 'This email is not registered.'
});
}
user.authenticate(password, function(authError, authenticated) {
if(authError) {
return done(authError);
}
if(!authenticated) {
return done(null, false, { message: 'This password is not correct.' });
} else {
return done(null, user);
}
});
})
.catch(err => done(err));
}
await can only be called within an async function - see the MDN documentation
Your function needs to be async function localAuthenticate(User, email, password, hostname, done).
The try/catch is the way to catch exceptions when using await, instead of the .then/.catch you are used to when dealing with Promises directly.
Your function would approximate, when using async/await:
async function localAuthenticate(User, email, password, hostname, done) {
try {
// Find user
let user = await User.findOne({ email: email.toLowerCase() }).exec()
if (!user) {
return done(null, false, { message: 'This email is not registered.' })
}
user.authenticate(password, function (authError, authenticated) {
if (authError) {
return done(authError)
}
if (!authenticated) {
return done(null, false, { message: 'This password is not correct.' });
}
return done(null, user);
})
} catch (err) {
done(err)
}
}
Further reading:
http://rossboucher.com/await/#/
https://ponyfoo.com/articles/understanding-javascript-async-await