Error handling in NODE JS
this is my Login Function
router.post('/admin/login', function (req, res) {
User.findOne({ username: req.body.username }, function (err, user) {
if (err) return res.status(500).send('Error on the server.');
if (!user) return res.status(404).send('No user found.');
var passwordIsValid = bcrypt.compareSync(req.body.password, user.password);
if (!passwordIsValid) return res.status(401).send({ auth: false, token: null });
if (req.body.username && req.body.password) {
console.log("enter");
var token = jwt.sign({ id: user._id }, config.secret, {
expiresIn: 86400 // expires in 24 hours
});
res.status(200).send({ auth: true, token: token });
} else {
return res.status(500).send('Error on the server.');
//res.status(500).send("Check Username & Password");
}
});
});
if i forget to enter password the server will be crashed how to handle on this
You need to check to see if the password is being passed before you pass it into the compareSync function.
if (!req.body.password) {
// handle error
return res.status(401).send("Missing or invalid password")
}
If you're doing this you should also check if req.body.username is being provided in the post request. Alternatively for easier method, you can have a try catch wrapped around the query to handle other unexpected errors.
It's better to check in front-end (client side) and also to check for email and password in back-end , there is various libraries to do that , for example i use express-validator lib here is a simple check and of course you can read full docs https://express-validator.github.io/docs/
code sample :
const { check, validationResult, body } = require('express-validator/check');
router.post('/admin/login',[check('email').isEmail(),check('password').isLength({ min: 5 })], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
// the rest of your sing in code here
})
and you can check for name length also this is safer for your back-end always check for validation in front-end and back-end, never trust client side only , do your own validation in the back-end , i hope this answer your question
I was having the same issue with a very similar code.
I replaced this line:
(...)
if (!user) return res.status(404).send('No user found.');
For this:
User.findOne({username : req.body.username},function(err, user){
if(err){
return res.status(401).json({message : err})
};
/* Trying to repair error when username is not on DB */
if(user === null) {
return res.status(401).json({auth : false, token : null, message : "Not Authorised User"});
};
/* Replacement worked */
var isPasswordValid = bcrypt.compareSync(req.body.password, user.password);
Hope it works for you.
Related
I am implementing a JSON API in Node ExpressJS and wondering if the best practice is to throw errors or return JSON to the caller indicating that something bad has happened.
This is my current implementation. If the username is already registered, I simply return back a message to the user. Should I be throwing an error instead etc?
const register = async (req, res) => {
const { username, password } = req.body
if (username.length == 0 || password.length == 0) {
// SHOULD I THROW AN ERROR
res.json({ success: false, message: 'Username or password is missing.' })
return
}
// check username is already registered
const existingUser = await models.User.findOne({
where: {
username: username
}
})
if (existingUser) {
// SHOULD I THROW AN ERROR
res.json({ success: false, message: 'Username already exists.' })
return
}
I am trying to log in using email and password but if I log in using the right data, it is fine. If I enter any one of the fields wrong then it shows the expected output and crashes instantly and I have to restart the server from npm start. No further API calls can be made afterward if I do not restart the server.
usersRoute:
// Login
router.post('/login', async (req, res) => {
try {
const user = await User.findOne({
email: req.body.email,
});
!user && res.status(401).json('email does not exist in the DB');
const bytes = CryptoJS.AES.decrypt(
user.password,
process.env.SECRET_KEY
);
const originalPassword = bytes.toString(CryptoJS.enc.Utf8);
originalPassword !== req.body.password &&
res.status(401).json('wrong password');
const accessToken = jwt.sign(
{
id: user._id,
},
process.env.SECRET_KEY,
{ expiresIn: '300000' }
);
const { password, ...info } = user._doc;
res.status(200).json({ ...info, accessToken });
} catch (err) {
res.status(500).json(err);
}
});
When you enter a bad credentials, the execution continues to the lines after
!user && res.status(401).json('email does not exist in the DB');
to stop execution of the handler, change this line to:
if(!user) { res.status(401).json('email does not exist in the DB'); return;}
Add a return statement to your res.json calls when you intend to exit the function early. Otherwise, the rest of your code continues to execute and throws an error because the response is already sent.
Your code is throwing an error, because your response is being sent multiple times. Then, your catch is trying to send another response, which throws yet another error.
for password most do originalPassword !== req.body.password && res.status(401).json('wrong password'); to if(Originalpassword !== req.body.password) { res.status(401).json('Wrong credentials password'); return;}
and for email !user && res.status(401).json('email does not exist in the DB');to if(!user) { res.status(401).json('Wrong credentials username'); return;}
i have a problem when i try to use a private api in my node.js server, This is the process that i am following
SignUp (if the user doesn't have an account) or logIn (already have an account).
It generates the token and i pass it in the header res.header('access_token': token)
I copy this token and paste it in my private api in the header section (i'm using postman to test it for now) in order to verify if the user is logged in.
In my route, i use a middleware to turn it private, validating if the user can use the resource jwt.verify(authorization[1], process.env.SEED_AUTH) (SEED_AUTH is my token secret stored in my server)
Here is when i have the error, the middleware is failling to verify the user and throw me this error jwt expired
This is my route
const UsersController = require('../controllers/users.controller')
const AuthorizationMiddleware = require('../middlewares/authorization.middleware')
exports.routesConfig = (app) => {
app.post('/api/users',[
AuthorizationMiddleware.verifyValidJWT
], UsersController.insert)
}
This is my middleware
const jwt = require('jsonwebtoken')
require('../config/env.config')
exports.verifyValidJWT = (req, res, next) => {
if (req.headers['access-token']) {
try {
let authorization = req.headers['access-token'].split(' ');
if (authorization[0] !== 'Bearer') {
return res.status(401).json({
ok: false,
err: "Unauthorized, Need a valid token"
});
} else {
console.log(authorization[1]);
req.jwt = jwt.verify(authorization[1], process.env.SEED_AUTH);
return next();
}
} catch (error) {
return res.status(403).json({
ok: false,
err: "Forbidden, need a valid token -> " + error.message
});
}
} else {
return res.status(401).json({
ok: false,
err: "Need to recieve a valid token"
});
}
}
And finally the API UsersController.insert
What i'm trying to do with this api is to create a new user.
For a better understanding this is my LOGIN API
const User = require('../models/users.model')
const { signUpValidation, logInValidation } = require('../middlewares/auth.validation.data')
const bcrypt = require('bcrypt')
const jwt = require('jsonwebtoken')
exports.logIn = async (req, res) => {
let body = req.body;
//Validation
const { error } = logInValidation(body)
if (error) {
return res.status(400).json({
ok: false,
err: error.details[0].message
})
}
//Check if the email already exists in the database
const user = await User.findOne({ email: body.email })
if (!user) {
return res.status(400).json({
ok: false,
err: "Invalid Email or password!"
})
}
const validPass = await bcrypt.compareSync(body.password, user.password)
if (!validPass) {
return res.status(400).json({
ok: false,
err: "Invalid Email or password!"
})
}
const token = jwt.sign(
{
_id: user._id,
email: user.email
},
process.env.SEED_AUTH,
{
expiresIn: process.env.TOKEN_EXPIRY
}
)
res.header('access-token', token).json({
ok: true,
user: {
id: user._id,
email: user.email
}
});
}
SignUp and LogIn validation
I use these middleware to verify if it is a valid email, and the name with a minimum number of letters...
My process.env.TOKEN_EXPIRY is set to 300 (i understand that, it is in seconds), i've tried with bigger number though
(The API works without the middleware).
What would be the problem that i am not seeing. Thanks for your help.
process.env variables are as string and not as a number. According to the jsonwebtoken documentation, string is considered as milliseconds by default and number is counted as seconds by default. So change TOKEN_EXPIRY to 300000 from 300
I am writing a sign-in function with my express app and do not like the fact that in the callback chain, lots of res.status(500).send(body) are duplicated:
router.post('/login', (req, res) => {
User.findOne({
where: { username: req.body.username }
})
.then( user => {
if (user) {
User.verifyPassword(req.body.password, user)
.then((verified) => {
if (verified) {
let signedToken = jwt.sign(
{ user: user.id },
'secret',
{ expiresIn: 24 * 60 * 60 }
);
res.status(200).send({
token: signedToken,
userId: user.id,
username: user.username
});
} else {
// If password entered does not match user password
res.status(500).send({ error: true, });
}
})
// If bycrpt explodes
.catch((error) => {
res.status(500).send({ error: error, });
});
} else {
// If we can't even find a user with that username
res.status(500).send({ error: true, });
}
})
// If the db query to find a user explodes
.catch(error => {
res.status(500).send({ error: error });
});
});
Two of these are related to vague exceptions occurring that make the API blow up. The other two are based on boolean values returned by the APIs.
I am not much of a back end engineer and this is just a personal project, but I want to know what are the best practices for this in the Node.js world.
While we're at it, I'm not sure what the appropriate status code to send in these error cases would be, as I am sure 500 is not correct.
I would rewrite your code like this, where we only have one .catch
router.post('/login', (req, res) => {
User.findOne({ where: { username: req.body.username }})
.then(user => {
if (!user) // If we can't even find a user with that username
return Promise.reject(true); // You should probably return an Error
return User.verifyPassword(req.body.password, user)
})
.then((verified) => {
if (!verified) // If password entered does not match user password
return Promise.reject(true); // You should probably return an Error
let signedToken = jwt.sign({
user: user.id
},
'secret', {
expiresIn: 24 * 60 * 60
}
);
res.status(200).send({
token: signedToken,
userId: user.id,
username: user.username
});
}).catch(error => {
// If the db query to find a user explodes
// If we can't even find a user with that username
// If password entered does not match user password
// You could throw different errors and handle
// all of them differently here
res.status(500).send({
error: error
});
});
});
This can be improved a little bit further, using async/await
router.post('/login', async(req, res) => {
try {
const user = await User.findOne({ where: { username: req.body.username }});
if (!user) // If we can't even find a user with that username
throw new Error('Invalid username');
const verified = await User.verifyPassword(req.body.password, user)
if (!verified) // If password entered does not match user password
throw new Error('Invalid password');
let signedToken = jwt.sign({
user: user.id
},
'secret', {
expiresIn: 24 * 60 * 60
}
);
res.status(200).send({
token: signedToken,
userId: user.id,
username: user.username
});
} catch(error) {
// If the db query to find a user explodes
// If we can't even find a user with that username
// If password entered does not match user password
res.status(500).send({
error: error.message
});
}
});
Regarding the status code, there are multiple ways to handle them, I usually throw a specific error for each status code.
errors.js
class Unauthorized extends Error {
constructor(message) {
super(message);
this.name = 'UnauthorizedError';
this.statusCode = 401
}
}
class BadRequest extends Error {
constructor(message) {
super(message);
this.name = 'BadRequestError';
this.statusCode = 400
}
}
/** more errors... **/
module.exports = {
Unauthorized,
BadRequest
};
So we can now set the right status code:
const { Unauthorized } = require('./errors');
/* ... */
try {
/* ... */
if (!verified) // Some people may say 422 status code...
throw new Unauthorized('Invalid password');
/* ... */
} catch(error) {
res.status(error.statusCode || 500).send({
error: error.message
});
}
While we're at it, I'm not sure what the appropriate status code to
send in these error cases would be, as I am sure 500 is not correct.
You're right that setting 500 for every error is not correct. I'll leave you a couple of questions that might help you set the correct status code, since it will be too long to discuss it in this question.
What's an appropriate HTTP status code to return by a REST API service for a validation failure?
What's the appropriate HTTP status code to return if a user tries logging in with an incorrect username / password, but correct format?
I am developing a android aplication with nodejs and postgreSQL, at the moment i just have the login and the register.
When i do a login and everything is fine the server send me a token, that token is stored on the device SharedPreference, now my confusion is, do i need to decode this token on every request, or do i need to do it just 1 time?
in this tutorial at the end, he decodes on every route the token, but i don't need to do that when i do for example a request to register.
What is the best way to implement this?
here is my server code:
//****************************************************Begin of login request **********************************/
router.post('/login', function (req, res, next) {
if (JSON.stringify(req.body) == "{}") {
return res.status(400).json({ Error: "Login request body is empty" });
}
if (!req.body.username || !req.body.password) {
return res.status(400).json({ Error: "Missing fields for login" });
}
// search a user to login
User.findOne({ where: { username: req.body.username } }) // searching a user with the same username and password sended in req.body
.then(function (user) {
if (user && user.validPassword(req.body.password)) {
//return res.status(200).json({ message: "loged in!" }); // username and password match
var payload = { user: user };
// create a token
var token = jwt.sign(payload, 'superSecret', {
expiresIn: 60 * 60 * 24
});
// return the information including token as JSON
res.json({
success: true,
message: 'Enjoy your token!',
token: token
});
}
else {
return res.status(401).json({ message: "Unauthorized" }); // if there is no user with specific fields send
}
}).catch(function (err) {
console.error(err.stack)
return res.status(500).json({ message: "server issues when trying to login!" }); // server problems
});
});
//****************************************************End of Login request **********************************/
//****************************************************Begin of register request******************************/
router.post('/register', function (req, res, next) {
if (JSON.stringify(req.body) == "{}") {
return res.status(400).json({ Error: "Register request body is empty" });
}
if (!req.body.email || !req.body.username || !req.body.password) {
return res.status(400).json({ Error: "Missing fields for registration" });
}
var password = User.generateHash(req.body.password);
User.create({
username: req.body.username,
email: req.body.email,
password: password
}).then(function () {
return res.status(200).json({ message: "user created" });
}).catch(function (err) {
return res.status(400).send({ message: err.message }); //
}).catch(function (err) {
return res.status(400).json({ message: "issues trying to connect to database" });
})
});
//****************************************************End of register request **********************************/
module.exports = router;
If you don't want to use JWT token check for all routes, you can skip those routes.
const url = require('url');
apiRoutes.use((req, res, next) => {
const path = url.parse(req.url).pathname;
console.log(path);
//No JWT token check
if (/^\/register/.test(path)) {
return next();
}
return jwtTokenValidate();
});
function jwtTokenValidate() {
// check header or url parameters or post parameters for token
var token = req.body.token || req.query.token || req.headers['x-access-token'];
// decode token
if (token) {
// verifies secret and checks exp
jwt.verify(token, app.get('superSecret'), function(err, decoded) {
if (err) {
return res.json({ success: false, message: 'Failed to authenticate token.' });
} else {
// if everything is good, save to request for use in other routes
req.decoded = decoded;
next();
}
});
} else {
// if there is no token
// return an error
return res.status(403).send({
success: false,
message: 'No token provided.'
});
}
}