cookies just keeps adding on and on when I register a new user.
userRoutes.js:
const { registerUser, loginUser, registerVerify } = require("./userController");
const express=require('express')
const router=express.Router()
router
.route('/register')
.post(registerUser)
module.exports=router
userController.js:
const User = require("./userModel");
const validator = require("validator");
const jwt=require('jsonwebtoken')
const sendToken = function (email) {
let data = {
expiresIn: process.env.JWT_EXPIRE_SHORT,
email,
};
let token = jwt.sign(data, process.env.JWT_SECRET_KEY);
return token;
};
exports.registerUser = async (req, res, next) => {
try {
const { userName, email, password, confirmPassword } = req.body;
let user = [
{ userName, email, password, confirmPassword, createdAt: Date.now() },
];
//passwords do not match error
if (password !== confirmPassword) {
throw "Password doesn't match Confirm Password.";
}
//email not valid error
if (!validator.isEmail(email)) {
throw "Email not valid.";
}
//user already logged in error
if (await User.findOne({ email })) {
throw "User already exists. Please login";
}
//create user
await User.create(user);
//assign token
let token = sendToken(email);
//clear cookie
res.status(200).clearCookie('token').cookie(token, 'token', {maxAge: 360000}).json({
message: "Please fill the required text send via email",
token,
});
} catch (err) {
// console.log(err)
res.status(400).json({
message: "Signup Not Successfull",
err,
});
}
};
I've tried:
router
.route('/register',{ credentials: 'same-origin'})
.post(registerUser)
router
.route('/register',{ credentials: 'include'})
.post(registerUser)
router
.route('/register',{ withCredentials: true})
.post(registerUser)
Here is the correct way for creating a cookie:
res.cookie('token', token, {maxAge: 360000});
Note: You don't need to clear and create a new one.
when you send a cookie with the same key it overrides itself.
Related
I put the wrong password but it gets login successfully and the data which is inside a particular user is as a new user login. I want you to please tell me a error.
Link of an online hosted project: NOTEBOOK/login
Link of GitHub repository: GITHUB/notebook
The login folder is inside routes/auth.js
const express = require('express');
const router = express.Router();
const User = require('../models/User')
const { body, validationResult } = require('express-validator');
var bcrypt = require('bcryptjs');
var jwt = require('jsonwebtoken');
let fetchuser = require('../middleware/fetchuser')
const dotenv = require('dotenv');
dotenv.config();
const secret=process.env.YOUR_SECRET
router.post('/login', [
body('email', 'Enter a valid Email').isEmail(),
body('password', 'Password cannot be blank').exists()
], async (req, res) => {
let success=false;
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { email, password } = req.body;
try {
// check whether user with this email exist
let user = await User.findOne({ email });
if (!user) {
return res.status(400).json({success, error: "Please login using correct credentials" })
};
let passwordCompare = bcrypt.compare(password, user.password);
if (!passwordCompare) {
return res.status(400).json({success, error: "Please login using correct credentials" })
};
const data = {
user: {
id: user.id
}
}
var authtoken = jwt.sign(data, secret);
success=true;
res.json({success, authtoken })
} catch (error) {
console.log(error.message);
res.status(500).send("Internal server error has occured")
}
})
you need to wait for the promise to resolve, you can opt for any of the below options
const passwordCompare = await bcrypt.compare(password, user.password);
OR
const passwordCompare = bcrypt.compareSync(password, user.password);
OR
bcrypt.compare(password, user.password).then(function(result) {
// result == true
});
I'm working on the backend with nodejs and mongodb . After coding authentication part i'm having unknown issue and i think it's from bcrypt :(
please have a look at my code.
USER MODEL CODE -
const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const validator = require("validator");
require("dotenv").config();
const userSchema = mongoose.Schema(
{
email: {
type: String,
required: true,
unique: true,
trim: true,
lowercase: true,
validate(value) {
if (!validator.isEmail) {
throw new Error("Invalid Email");
}
},
},
password: {
type: String,
required: true,
trim: true,
},
role: {
type: String,
enum: ["user", "admin"],
default: "user",
},
name: {
type: String,
required: true,
maxlength: 21,
},
phone: {
required: true,
type: Number,
maxlength: 12,
},
},
{
timestamps: true,
},
);
userSchema.pre("save", async function (next) {
if (this.isModified("password")) {
// hash the password
const salt = await bcrypt.genSalt(10);
const hash = await bcrypt.hash(this.password, salt);
this.password = hash;
}
next();
});
userSchema.methods.generateToken = function () {
const userObj = { id: this._id.toHexString(), email: this.email };
const token = jwt.sign(userObj, process.env.DB_SECRET, {
expiresIn: "3d",
});
return token;
};
userSchema.statics.emailTaken = async function (email) {
const user = await this.findOne({ email });
return !!user;
};
const User = mongoose.model("User", userSchema);
module.exports = {
User,
};
USER API CODE (Please avoid those bunch of console log statements as i had added them for debugging purpose) -
const express = require("express");
const router = express.Router();
require("dotenv").config();
const { User } = require("../../models/userModel");
const bcrypt = require("bcrypt");
// const comparePassword = async (userPassword) => {
// console.log("Comparing password with bcrypt");
// const match = await bcrypt.compare(userPassword, this.password);
// console.log(match);
// return match;
// };
router.route("/signup").post(async (req, res) => {
const { email, password, name, phone } = req.body;
console.log(req.body);
try {
// Check if user email exists
if (await User.emailTaken(email)) {
return res.status(400).json({ message: "Email already exists" });
}
// create user instance and hash password
const user = new User({
email: email,
password: password,
name: name,
phone: phone,
});
// generate jwt token
console.log("user is saving");
const token = user.generateToken();
const userDoc = await user.save();
// send email
// save....send token with cookie
res.cookie("access-token", token).status(200).send(returnUserDoc(userDoc));
} catch (error) {
res
.status(400)
.json({ message: "Error while creating user", error: error });
}
});
router.route("/login").post(async (req, res) => {
try {
// Find user
console.log("Finding User........");
let user = await User.find({ email: req.body.email });
if (!user) {
return res.status(400).json({ message: "Invalid Credentials" });
}
// Compare Password
console.log("Comparing password with bcrypt");
const match = await bcrypt.compare(req.body.password, user.password);
console.log("password compared");
console.log(match);
if (match == false) {
return res.status(400).json({ message: "Invalid Credentials" });
}
console.log("Password Matched");
// Generate Token
console.log("Generating Token........");
const token = user.generateToken();
// Response
console.log("Sending Response........");
res.cookie("access-token", token).status(200).send(returnUserDoc(user));
} catch (error) {
console.log("Error");
res.status(400).json({ message: "Error while loggin in", error: error });
}
});
// functions
const returnUserDoc = (userDoc) => {
return {
_id: userDoc._id,
name: userDoc.name,
email: userDoc.email,
phone: userDoc.phone,
};
};
module.exports = router;
CONSOLE LOGS -
listening on port 3001
Database Connected
Finding User........
Comparing password with bcrypt
Error
I have found that code is executing successfully just before const match = await bcrypt.compare(req.body.password, user.password); this line
On console.log(error.message); i'm getting this -
Error: data and hash arguments required
at Object.compare (D:\CovidHelpers\CovidHelpers\node_modules\bcrypt\bcrypt.js:208:17)
at D:\CovidHelpers\CovidHelpers\node_modules\bcrypt\promises.js:29:12
at new Promise (<anonymous>)
at Object.module.exports.promise (D:\CovidHelpers\CovidHelpers\node_modules\bcrypt\promises.js:20:12)
at Object.compare (D:\CovidHelpers\CovidHelpers\node_modules\bcrypt\bcrypt.js:204:25)
at D:\CovidHelpers\CovidHelpers\server\routes\api\users.js:60:32
at processTicksAndRejections (internal/process/task_queues.js:93:5)
data and hash arguments required
Please help me fix this :)
Thank You
const express = require('express');
const router = express.Router();
const auth = require('../../middleware/auth');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const config = require('config');
const { check, validationResult } = require('express-validator');
const User = require('../../models/User');
// #route Get api/auth
// #desc Test route
// #access Public
router.get('/', auth, async (req, res) => {
try {
const user = await User.findById(req.user.id).select('-password');
res.json(user);
} catch(err) {
console.error(err.message);
res.status(500).send('Server Error')
}
});
// #route POST api/auth
// #desc Authenticate User And Get Token
// #access Public
router.post('/',
[
check('email', 'Please include a valid email').isEmail(),
check('password', 'Password is required').exists()
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array()});
}
const { email, password } = req.body;
try {
// See if user exists
let user = await User.findOne({ email})
if (!user) {
return res
.status(400)
.json({ errors: [ { msg: 'Invalid Credentials Email' } ] });
}
// Make Sure Password matches
const isMatch = await bcrypt.compare(password, user.password);
if(!isMatch) {
return res
.status(400)
.json({ errors: [ { msg: 'Invalid Credentials Password' } ] });
}
const payload = {
user: {
id: user.id
}
}
jwt.sign(
payload,
config.get('jwtSecret'),
{ expiresIn: 360000 },
(err, token) => {
if(err) throw err;
res.json({ token });
}
);
} catch(err) {
console.error(err.message);
res.status(500).send('Server error')
}
});
module.exports = router
In my database I have email and password
in my postman when i make POST request to https://localhost:5000/api/auth
the email is correct however I keep getting password is not correct with this const isMatch = await bcrypt.compare(password, user.password);
if(!isMatch) {
return res
.status(400)
.json({ errors: [ { msg: 'Invalid Credentials Password' } ] });
}
i console logged both password and user.password and it is the same value, i dont understand why !isMatch is keep getting triggered
can anyone help me
The syntax of bcrypt.compare() is:
bcrypt.compare(plaintextPassword, hash)...
The first parameter is plaintext password and second is hash of the real password so it will not be the same value. As you mentioned that your password and user.password is the same value, I guess you forgot to hash user password before saving it to the DB. Please check the docs for more details.
I was following a tutorial at Authentication in NodeJS With Express and Mongo - CodeLab #1
I got everything to work perfectly, but the tutorial does not address how to log out a user.
From what I can tell, the session is being saved on Mongoose Atlas, which is the database I am using. When I log a user in with Postman, I get a token back. But I am not sure how to configure the /logout route.
Here is my code:
//routes/user.js
const express = require("express");
const { check, validationResult } = require("express-validator");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const router = express.Router();
const auth = require("../middleware/auth");
const User = require("../models/User");
/**
* #method - POST
* #param - /signup
* #description - User SignUp
*/
//Signup
router.post(
"/signup",
[
check("username", "Please Enter a Valid Username")
.not()
.isEmpty(),
check("email", "Please enter a valid email").isEmail(),
check("password", "Please enter a valid password").isLength({
min: 6
})
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array()
});
}
const {
username,
email,
password
} = req.body;
try {
let user = await User.findOne({
email
});
if (user) {
return res.status(400).json({
msg: "User Already Exists"
});
}
user = new User({
username,
email,
password
});
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
await user.save();
const payload = {
user: {
id: user.id
}
};
jwt.sign(
payload,
"randomString", {
expiresIn: 10000
},
(err, token) => {
if (err) throw err;
res.status(200).json({
token
});
}
);
} catch (err) {
console.log(err.message);
res.status(500).send("Error in Saving");
}
}
);
// Login
router.post(
"/login",
[
check("email", "Please enter a valid email").isEmail(),
check("password", "Please enter a valid password").isLength({
min: 6
})
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array()
});
}
const { email, password } = req.body;
try {
let user = await User.findOne({
email
});
if (!user)
return res.status(400).json({
message: "User Not Exist"
});
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch)
return res.status(400).json({
message: "Incorrect Password !"
});
const payload = {
user: {
id: user.id
}
};
jwt.sign(
payload,
"randomString",
{
expiresIn: 3600
},
(err, token) => {
if (err) throw err;
res.status(200).json({
token
});
}
);
} catch (e) {
console.error(e);
res.status(500).json({
message: "Server Error"
});
}
}
);
// router.route("/logout").get(function (req, res, next) {
// if (expire(req.headers)) {
// delete req.user;
// return res.status(200).json({
// "message": "User has been successfully logged out"
// });
// } else {
// return next(new UnauthorizedAccessError("401"));
// }
// });
router.get("/me", auth, async (req, res) => {
try {
// request.user is getting fetched from Middleware after token authentication
const user = await User.findById(req.user.id);
res.json(user);
} catch (e) {
res.send({ message: "Error in Fetching user" });
}
});
router.get('/logout', isAuthenticated, function (req, res) {
console.log('User Id', req.user._id);
User.findByIdAndRemove(req.user._id, function (err) {
if (err) res.send(err);
res.json({ message: 'User Deleted!' });
})
});
module.exports = router;
function isAuthenticated(req, res, next) {
console.log("req: " + JSON.stringify(req.headers.authorization));
// if (!(req.headers && req.headers.authorization)) {
// return res.status(400).send({ message: 'You did not provide a JSON web token in the authorization header' });
//}
};
///middleware/auth.js
const jwt = require("jsonwebtoken");
module.exports = function (req, res, next) {
const token = req.header("token");
if (!token) return res.status(401).json({ message: "Auth Error" });
try {
const decoded = jwt.verify(token, "randomString");
req.user = decoded.user;
next();
} catch (e) {
console.error(e);
res.status(500).send({ message: "Invalid Token" });
}
};
///models/User.js
const mongoose = require("mongoose");
const UserSchema = mongoose.Schema({
username: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now()
}
});
// export model user with UserSchema
module.exports = mongoose.model("user", UserSchema);
So my question is, how can I implement a /logout route so that if the user clicks the logout button and invokes that route, their token is destroyed. I am only asking about the back-end part. I can handle using axios.
Thanks.
From what I see, you are not saving any session data or storing tokens anywhere - which is great. You are simply appending the token to your headers in requests to the API.
So the only thing you can do is possibly expire the token in the /logout route
and then ensure you delete the token on the client - could be localStorage, sessionStorage etc - your client code needs to kill the token so it cannot be included again.
Side note:
You are not extending the token lifetime anywhere, so even if the user keeps interacting on the website, the token expiration is not being updated. You will need to manually refresh the token/generate a new token to have a sliding expiration.
I would suggest you save the token in cookies rather. Set the cookie to HttpOnly, Secure, and specify the domain. This is far more secure and will allow you to also expire the cookie from the API. If any scripts you include get compromised, they can access all your users’ tokens easily.
Example:
import {serialize} from 'cookie';
import jsend from 'jsend';
...
const token = jwt.sign(
{
id: validationResult.value.id // whatever you want to add to the token, here it is the id of a user
},
privateKeyBuffer,
{
expiresIn: process.env.token_ttl,
algorithm: 'RS256'
});
const cookieOptions = {
httpOnly: true,
path: '/',
maxAge: process.env.token_ttl,
expires: new Date(Date.now() + process.env.token_ttl),
sameSite: process.env.cookie_samesite, // strict
domain: process.env.cookie_domain, // your domain
secure: process.env.cookie_secure // true
};
const tokenCookie = await serialize('token', token, cookieOptions);
res.setHeader('Set-Cookie', [tokenCookie]);
res.setHeader('Content-Type', 'application/json');
res.status(200).json(jsend.success(true));
Then in logout:
// grab from req.cookies.token and validate
const token = await extractToken(req);
// you can take action if it's invalid, but not really important
if(!token) {
...
}
// this is how we expire it - the options here must match the options you created with!
const cookieOptions = {
httpOnly: true,
path: '/',
maxAge: 0,
expires: 0,
sameSite: process.env.cookie_samesite, // strict
domain: process.env.cookie_domain, // your domain
secure: process.env.cookie_secure // true
};
// set to empty
const tokenCookie = await serialize('token', '', cookieOptions);
res.setHeader('Set-Cookie', [tokenCookie]);
res.setHeader('Content-Type', 'application/json');
res.status(200).json(jsend.success(true));
As you have used JWT the backend will always check 2 things
1. Proper token
2. If time is over for that particular (You should handle this one)
For 2nd point, if the user time is up than from frontend you may delete the token if you have stored the token in localstorage.
For logout when user clicks on logout just delete jwt from localstorage and redirect to login or other page
I am developping a web app with the MERN stack.
On Postman or on my front-end form, when I register a user with an existing email, the user is not added to database. I use the same logic to check if the username picked by the user is already taken. If it's taken, the error message is dispatched but the user is still added to the database.
In the User model, the field Username is unique, just like the field Email.
My register route:
const express = require("express");
const router = express.Router();
const User = require("../../models/User");
const { check, validationResult } = require("express-validator");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const config = require("config");
// #route GET register
// #desc Register
// #access Public
router.get("/", (req, res) => {
res.send("Create an account");
});
// #route POST register
// #desc Register
// #access Public
router.post(
"/",
[
check("email", "Please, include a valid email.").isEmail(),
check(
"password",
"Please, enter a password with 6 or more characters"
).isLength({ min: 6 }),
check("username", "Please, include a valid username.")
.not()
.isEmpty()
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const {
email,
password,
birthdate,
birthplace,
sun,
username,
date,
is_paying
} = req.body;
try {
// See if user exists
let user = await User.findOne({ $or: [{ username }, { email }] });
if (user) {
res.status(400).json({ errors: [{ msg: "User already exists" }] });
}
// Create new user from User Model
user = new User({
email,
password,
birthdate,
birthplace,
sun,
username,
date,
is_paying
});
// Encrypt password
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
// Add user to database
await user.save();
// Return JWT
const payload = {
user: {
id: user.id
}
};
jwt.sign(
payload,
config.get("jwtSecret"),
{ expiresIn: 360000 },
(err, token) => {
if (err) throw err;
res.json({ token });
}
);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
}
);
module.exports = router;
My register action :
// REGISTER USER
export const register = ({
email,
password,
birthdate,
gender,
sun,
username
}) => async dispatch => {
const config = {
headers: {
"Content-Type": "application/json"
}
};
const body = JSON.stringify({
email,
password,
birthdate,
gender,
sun,
username
});
try {
const res = await axios.post("/register", body, config);
dispatch({
type: REGISTER_SUCCESS,
payload: res.data
});
dispatch(loadUser());
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach(error => dispatch(setAlert(error.msg, "danger")));
}
dispatch({
type: REGISTER_FAIL
});
}
};
You need to stop processing the request if the user exists:
// See if user exists
let user = await User.findOne({ $or: [{ username }, { email }] });
if (user) {
res.status(400).json({ errors: [{ msg: "User already exists" }] });
}
// Create new user from User Model
user = new User({
email,
password,
birthdate,
birthplace,
sun,
username,
date,
is_paying
});
See that? if (user) { res.json() } but you still go on. Make that return res.json() for a quick fix.
A bit better fix is you should set a unique index on the username and on the email fields. In mongoose it looks something like:
const userSchema = new Schema({
name: {
type: String,
index: {
unique: true
}
}
});
Or if you want to allow same usernames but the uniqueness to be the username-email combo, make a compound index.
And even better - move the whole thing to another file. Call the file user-registration.service.js. Make the file export one function:
async function registerUser(username, email, password) {
// check uniqueness here. if it fails, _throw an error!_
// if all is ok, save the user, return the details.
}
module.exports = {
registerUser
}
That way your route controller can just say:
router.post(
"/",
[
check("email", "Please, include a valid email.").isEmail(),
check(
"password",
"Please, enter a password with 6 or more characters"
).isLength({ min: 6 }),
check("username", "Please, include a valid username.")
.not()
.isEmpty()
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const {
email,
password,
birthdate,
birthplace,
sun,
username,
date,
is_paying
} = req.body;
try {
const user = await registerUser();
return res.json(user); // or token or whatnot.
} catch (err) {
// see here? only one place to deal with that error.
return res.status(400)
.json(err);
}
}
Now you only have that one place to deal with errors. You could even push it further and simply omit that try/catch and let a global error handler deal with it.
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const {
email,
password,
birthdate,
birthplace,
sun,
username,
date,
is_paying
} = req.body;
try {
// See if user exists
let user = await User.findOne({ $or: [{ username }, { email }] });
if (user) {
res.status(400).json({ errors: [{ msg: "User already exists" }] });
}
else{
// Create new user from User Model
user = new User({
email,
password,
birthdate,
birthplace,
sun,
username,
date,
is_paying
});
// Encrypt password
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
// Add user to database
await user.save();
// Return JWT
const payload = {
user: {
id: user.id
}
};
jwt.sign(
payload,
config.get("jwtSecret"),
{ expiresIn: 360000 },
(err, token) => {
if (err) throw err;
res.json({ token });
}
);
}
}
catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
}
);
Add the add new user code in else section