So I'm trying to build a very basic user login. I'm trying to create a user, then login with those credentials and get back a JSON Web Token. Where I'm stuck is trying to compare the passwords then send a response.
Steps:
Create User:
enter email and password
salt/hash user password
store user into database
return success
Login
find user by request email value
if found compare passwords
passwords good send JSON Web Token
User Model
email:{
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
}
User Routes
var express = require('express');
var router = express.Router();
var jwt = require('jsonwebtoken');
var bcrypt = require('bcryptjs');
// Create User
...
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash("superSecret", salt, function(err, hash) {
user.password = hash;
user.save();
res.json({success: true, message: 'Create user successful'});
});
});
...
// Login
...
bcrypt.compare(req.body.password, 'superSecret', function(err, res) {
if(req.body.password != user.password){
res.json({success: false, message: 'passwords do not match'});
} else {
// Send JWT
}
});
So the two problems here is that, I can't send a response nor can I compare the password. Just completely stuck on this, any help would be greatly appreciated.
As described in the doc, you should use bcrypt.compare like that:
bcrypt.compare(req.body.password, user.password, function(err, res) {
if (err){
// handle error
}
if (res) {
// Send JWT
} else {
// response is OutgoingMessage object that server response http request
return response.json({success: false, message: 'passwords do not match'});
}
});
And here is a nice post about Password Authentication with Mongoose (Part 1): bcrypt
//required files
const express = require('express')
const router = express.Router();
//bcryptjs
const bcrypt = require('bcryptjs')
//User modal of mongoDB
const User = require('../../models/User')
//Post request for login
router.post('/', (req, res) => {
//email and password
const email = req.body.email
const password = req.body.password
//find user exist or not
User.findOne({ email })
.then(user => {
//if user not exist than return status 400
if (!user) return res.status(400).json({ msg: "User not exist" })
//if user exist than compare password
//password comes from the user
//user.password comes from the database
bcrypt.compare(password, user.password, (err, data) => {
//if error than throw error
if (err) throw err
//if both match than you can do anything
if (data) {
return res.status(200).json({ msg: "Login success" })
} else {
return res.status(401).json({ msg: "Invalid credencial" })
}
})
})
})
module.exports = router
If we you to use bcryptjs in browser(HTML) then you can add bcryptjs CDN to do this.
CDN - https://cdn.jsdelivr.net/npm/bcryptjs#2.4.3/dist/bcrypt.js
Example-
HTML- (Add above CDN in tag)
JS-
var bcrypt = dcodeIO.bcrypt;
/** One way, can't decrypt but can compare */
var salt = bcrypt.genSaltSync(10);
/** Encrypt password */
bcrypt.hash('anypassword', salt, (err, res) => {
console.log('hash', res)
hash = res
compare(hash)
});
/** Compare stored password with new encrypted password */
function compare(encrypted) {
bcrypt.compare('aboveusedpassword', encrypted, (err, res) => {
// res == true or res == false
console.log('Compared result', res, hash)
})
}
If you want to do same in Nodejs
/** Import lib like below and use same functions as written above */
var bcrypt = require('bcryptjs')
From what I can see your logic is correct.
If you are using mongoose I suggest you to use the pre 'save' hook.
User Schema
userSchema.pre('save', function(next) {
// only hash the password if it has been modified (or is new)
if (!this.isModified('password')) {
return next();
}
// generate a salt
return bcrypt.genSalt(10, function(error, salt) {
if (error) {
return next(error);
}
// hash the password using the new salt
return bcrypt.hash(this.password, salt, function(error, hash) {
if (error) {
return next(error);
}
// override the cleartext password with the hashed one
this.password = hash;
return next();
});
});
});
userSchema.methods.comparePassword = function(passw, cb) {
bcrypt.compare(passw, this.password, function(err, isMatch) {
if (err) {
return cb(err, false);
}
return cb(null, isMatch);
});
};
And in your routes:
Login
...
return user.comparePassword(password, function(error, isMatch) {
var payload = {
iat: Math.round(Date.now() / 1000),
exp: Math.round((Date.now() / 1000) + 30 * 24 * 60),
iss: 'Whatever the issuer is example: localhost:3000',
email: user.email
};
var token = jwt.encode(payload, 'secret');
if (isMatch && !error) {
// if user is found and password is right create a token
return res.json({
success: true,
token: `JWT ${token}`,
user: user,
msg: 'Authentication was succesful'
});
}
return next({code: 401, msg: 'Password is incorrect'});
});
});
Create user
// Pre hook will take care of password creation
return user.save()
.then(function(user) {
var payload = {
iat: Math.round(Date.now() / 1000),
exp: Math.round((Date.now() / 1000) + 30 * 24 * 60),
iss: 'Whatever the issuer is example: localhost:3000',
email: user.email
};
var token = jwt.encode(payload, 'secret');
return res.status(201).json({user, token: `JWT ${token}`, msg: 'User was succesfully created'});
})
.catch((err) => next(err));
bcrypt.compare(req.body.password, user.password, function(err, results){
if(err){
throw new Error(err)
}
if (results) {
return res.status(200).json({ msg: "Login success" })
} else {
return res.status(401).json({ msg: "Invalid credencial" })
}
})
const bcrypt = require("bcryptjs");
const salt = bcrypt.genSaltSync(10);
const hashPassword = (password) => bcrypt.hashSync(password, salt);
const comparePassword = (password, hashedPassword) =>
bcrypt.compareSync(password, hashedPassword);
bcrypt.compare(req.body.password, user.password)
.then(valid => {
if (!valid) {
return res.status(401).json({ message: 'Paire login/mot de passe incorrecte' });
}
res.status(200).json({
userId: user._id,
token:jwt.sign(
{userId: user._id},
process.env.ACCESS_TOKEN_SECRET_KEY,
{expiresIn:'24h'}
),
message: 'connected'
});
})
.catch(error => res.status(500).json({ error }));
enter code here
Related
I have a post route request function below to login a user. I keep getting 401 unauthorized errors when attempting to make the request. Based on my code below, is there any refactoring I can do to fix this? Many thanks!!!
router.post('/login', async (req, res, next) => {
try {
// attempt to find the user in database //
const user = await User.findOne({ username: req.body.username });
// if user entered doesn't match which is in the database throw an error //
if (!user) {
res.status(401).json('wrong credentials!')
var hashedPassword = Cryptojs.AES.decrypt(
user.password,
process.env.PASS_SEC);
var Orginalpassword = hashedPassword.toString(Cryptojs.enc.Utf8);
}
// check if password entered matches the orignal password entered during registration, if not return error //
else if ( Orginalpassword !== req.body.password ) {
res.status(401).json('wrong credentials!');
var accessToken = jwt.sign({
id: user._id,
isAdmin: user.isAdmin
},
process.env.JWT_SEC,
{expiresIn:'3d'}
);
var { password, ...others} = user._doc;
}
else {
// if password and username both match successfully log user in //
return res.status(200).json({...others, accessToken})
}
} catch (error) {
res.status(500).json(error);
}
});
here i did the simplest way to implement the login API try this👇🏻
router.post('/login', async (req, res, next) => {
try {
const {username, password} = req.body;
// attempt to find the user in database
const user = await User.findOne({ username });
// compares the password
if (user && await bcrypt.compare(password, user.password)) {
let accessToken = jwt.sign({
id: user._id,
isAdmin: user.isAdmin
},
process.env.JWT_SEC,
{expiresIn:'3d'}
);
user.accessToken = accessToken;
return res.status(200).json({...others, accessToken})
}
return res.status(401).send('wrong credentials!');
} catch (error) {
return res.status(500).json(error.message);
}
});
I have a auth.js file And a middleware named as fetchuser code given beolow
Can anyone please tell me why am i getting this error.
I am using express js and mongoose but this error is occured during sending token to the user and verify the user whether is user logged in or not.
auth.js
const express = require('express');
const User = require('../models/User');
const router = express.Router();
const { body, validationResult } = require('express-validator');
const bcrypt = require('bcryptjs'); // it is used for password hashing
const jwt = require('jsonwebtoken');
const fetchuser=require('../middleware/fetchuser');
// Route:1 - Create a User using :POST. "/api/auth/createuser". NO Login Required.
router.post('/createuser', [
body('email', 'Enter valid email').isEmail(),
body('name', 'Enter valid email').isLength({ min: 3 }),
body('password').isLength({ min: 5 })
], async (req, res) => {
// Check fo vaidation whether is any rule(defined in User model) breaked or not
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Check Whether user with same email id exist or not
try {
let user = await User.findOne({ email: req.body.email });
if (user) {
return res.status(400).json({ error: "Sorry user with same email id already exist" });
}
// hashing of password
const salt = await bcrypt.genSalt(10);
const securePassword = await bcrypt.hash(req.body.password, salt);
// create A new User
user = await User.create({
name: req.body.name,
email: req.body.email,
password: securePassword
})
// returning user id in Token
const JWT_secret = "Rishiisa#boy";
const data = { user:{id: user.id} };
const auth_token = jwt.sign(data, JWT_secret);
res.json({ auth_token });
}
catch (error) {
console.error(error.message);
res.status(500).send("Internal server error");
}
})
// Route:2 - Login a User using credential. "/api/auth/login". NO Login Required.
router.post('/login', [
body('email', 'Enter valid email').isEmail(),
body('password', 'password can not be blank').exists(),
], async (req, res) => {
// Check for vaidation according to the rule defined at line no. 53, 54;
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// destructure the email and password from body request
const { email, password } = req.body;
try {
// Checking whether email is exist or not
let user = await User.findOne({ email });
if (!user) {
return res.status(400).json({ error: "Please try to login using correct credentials" });
}
// Now Comparing password with help of bcryptjs
const comparepassword = await bcrypt.compare(password, user.password);
if (!comparepassword) {
return res.status(400).json({ error: "Please try to login using correct credentials" });
}
// Now if user enter coorect password and login then user got logged in;
// And We will send authtoken to user;
// returning user id in Token
const JWT_secret = "Rishiisa#boy";
const data = { user:{id: user.id} };
const auth_token = jwt.sign(data, JWT_secret);
res.json({ auth_token });
}
catch (error) {
console.error(error.message);
res.status(500).send("Internal server error");
}
})
// Route:3 - Get Loggedin User details using:POST "/api/auth/getuser" Login required
router.post('/getuser', fetchuser, async (req, res) => {
try {
const userid = req.user.id;
const user = await User.findById(userid).select("-password");
res.send(user);
} catch (error) {
console.error(error.message);
res.status(500).send("Internal server error");
}
})
module.exports = router
middleware:
fetchuser.js
const jwt = require('jsonwebtoken');
const JWT_secret = "Rishiisa#boy";
const fetchuser = (req, res, next) => {
// Get the user from jwt token and add user id to req object
const token = req.header('auth_token');
if (!token) {
res.status(401).send({ error: "Please authenticate using a valid token" });
}
try {
const data = jwt.verify(token, JWT_secret);
req.user = data.user;
next();
} catch (error) {
res.status(401).send({ error: "Please authenticate using a valid token" });
}
}
module.exports = fetchuser;
In auth.js, where you wrote: "const data = { user:{id: user.id} };" Try changing user.id to user._id, since in MongoDB the user id is referred to as '_id'.
Let me know if that works.
I've had problems sending jwt token back and even verifying it, but all is good on my side now.
Also, below is my (inspired) method of going about this:
router.post('/register', (req, res)=>{
const { username, password } = req.body;
const user = new User({
username,
password
});
bcrypt.genSalt(10, (err, salt)=>{
bcrypt.hash(user.password, salt, (err, hash)=>{
if(err) throw err;
user.password = hash;
user.save()
.then(user=>{
jwt.sign(
{ id: user._id },
process.env.jwtSecret,
{ expiresIn: 3600 },
(err, token) =>{
if(err) throw err;
res.status(200)
}
)
})
})
})
})
I am working through an authentication application from devchallenges.io. So far, I've been able to set up the basic login with email and password. I'm now trying to set up google login auth. When testing the server with Postman I get the error Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client. The app seems to stills work as a json web token is returned so not too sure why I'm getting this error.
server.js
// #route POST auth/google
// #desc auth user with google
// #access Public
app.post('/auth/google', (req, res) => {
const { token } = req.body;
const CLIENT_ID =
'1068367393637-jtuoidgq5mi5krd5q31u9ncjovt3fvgh.apps.googleusercontent.com';
const client = new OAuth2Client(CLIENT_ID);
let { email, name, picture, password } = '';
async function verify() {
const ticket = await client.verifyIdToken({
idToken: token,
audience: CLIENT_ID,
});
const payload = ticket.getPayload();
const userid = payload['sub'];
email = payload.email;
name = payload.name;
picture = payload.picture;
password = generator.generateMultiple({
length: 6,
});
try {
// See if user exists
let user = await AuthUser.findOne({ email });
if (user) {
// Return jsonwebtoken
const payload = {
user: {
id: user.id,
},
};
jwt.sign(
payload,
config.get('jwtSecret'),
{ expiresIn: '5 days' },
(err, token) => {
if (err) throw err;
res.json({ token });
}
);
}
user = new AuthUser({
name,
email,
picture,
password,
});
// Encrypt password
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
await user.save();
// Return jsonwebtoken
const payload = {
user: {
id: user.id,
},
};
jwt.sign(
payload,
config.get('jwtSecret'),
{ expiresIn: '5 days' },
(err, token) => {
if (err) throw err;
res.json({ token });
}
);
} catch (error) {
console.error(error.message);
res.status(500).send('Server error');
}
}
verify().catch(console.error);
});
All I had to do was add an else conditional statement.
if (user) {
// Return jsonwebtoken
const payload = {
user: {
id: user.id,
},
};
jwt.sign(
payload,
config.get('jwtSecret'),
{ expiresIn: '5 days' },
(err, token) => {
if (err) throw err;
res.json({ token });
}
);
} else {
user = new AuthUser({
name,
email,
picture,
password,
});
// Encrypt password
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
await user.save();
// Return jsonwebtoken
const payload = {
user: {
id: user.id,
},
};
jwt.sign(
payload,
config.get('jwtSecret'),
{ expiresIn: '5 days' },
(err, token) => {
if (err) throw err;
res.json({ token });
}
);
}
I am currently working on a "Basic Authetntication" for Node JS. It should accept requests like the following:
POST http://localhost:8080/authenticate/
Authorization: Basic YWRtaW46MTIz
The AuthenticationService.js first reads the header and then passes the whole thing to the Userservice.js
AuthenticationService.js
async function basicAuth(req, res, next) {
// make authenticate path public
if (req.path === '/') {
return next();
}
if (!req.headers.authorization || req.headers.authorization.indexOf('Basic ') === -1) {
return res.status(401).json({ message: 'Missing Authorization Header' });
}
// verify auth credentials
const base64Credentials = req.headers.authorization.split(' ')[1];
const credentials = Buffer.from(base64Credentials, 'base64').toString('ascii');
const [username, password] = credentials.split(':');
console.log("AuthenticationService "+username+" "+password);
const user = await userService.authenticate({ username, password });
if (!user) {
return res.status(401).json({ message: 'Invalid Authentication Credentials' });
}
req.user=user
res.send(user)
next();
}
module.exports = {
basicAuth
}
The user service checks if the user is found and checks if the password is valid, only then the user object is sent back to the authentication service.
UserService.js
async function authenticate({ username, password }) {
let user = await User.findOne({userID: username})
user.comparePassword(password.toString(), function(err,isMatch) {
if (err){
console.log("error")
throw err;
}
if(isMatch)
{
console.log("Password correct")
}
if(!isMatch){
console.log("Password wrong")
}});
if(user){
return user;
}
else{
return null;
}
}
module.exports = {
authenticate
}
The .comparePassword-Method is inside the Usermodel.js:
UserSchema.methods.comparePassword = function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
if (err) return cb(err);
cb(null, isMatch);
});
};
const User = mongoose.model("User", UserSchema);
module.exports = User;
How can I send the boolean value of isMatch in the Userservice.js outside it's scope, so I can send the userobject back to the AuthenticationService.js depending on the correct password ? How can I improve that code ?
I erase the authenticate-method in Userservice.js and just call the crud-method. After that I call the compare-method and inside the if/else-block I pass a res.send.
function basicAuth(req, res, next) {
if (!req.headers.authorization || req.headers.authorization.indexOf('Basic ') === -1) {
return res.status(401).json({
message: 'Missing Authorization Header'
});
}
// verify auth credentials
const base64Credentials = req.headers.authorization.split(' ')[1];
const credentials = Buffer.from(base64Credentials, 'base64').toString('ascii');
const [username, password] = credentials.split(':');
console.log("AuthenticationService " + username + " " + password);
userService.findUserById(username, function(error, user) {
user.comparePassword(password.toString(), function(err, isMatch) {
if (err) {
console.log("Fehler")
throw err;
}
/*Passwort richtig*/
if (isMatch) {
res.send(user);
}
/*Passwort falsch*/
if (!isMatch) {
res.status(401).json({
message: 'Passwort und userID stimmen nicht ĂĽberein.'
});
}
});
})
}
I have a node.js login system with passport but I am trying to figure out how to log in a user with either their username or email. I am only able to log in the user with email or username seperately. I don't know how to write the code to cater for both their username and email. So if a user wants to login with username, they can or if the wish to use their email, they also can. Here is my localstrategy code in my users.js file:
passport.use(new LocalStrategy(
function(email, password, done) {
User.getUserByEmail(email, function(err, user, next){
if(err) throw err;
if(!user){
return done(null, false, {message: 'Unknown user'});
}
User.comparePassword(password, user.password, function(err, isMatch){
if(err) throw err;
if(isMatch){
return done(null, user);
} else {
return done(null, false, {message: 'Invalid password'});
}
});
});
}));
And here's my module.exports in my user.js:
module.exports.getUserByEmail = function(email, callback){
var query = {email: email};
User.findOne(query, callback);
}
module.exports.getUserById = function(id, callback){
User.findById(id, callback);
}
module.exports.comparePassword = function(candidatePassword, hash, callback){
bcrypt.compare(candidatePassword, hash, function(err, isMatch) {
if(err) throw err;
callback(null, isMatch);
});
}
The above code only allows user to login with their email. I want users to have the opportunity to login with either their email or username.
Just got into this situation, and I though I'll share my final code to verify user using either username or email:
userSchema.statics.findByCredentials = async credentials => {
const {
email = undefined,
username = undefined,
password = undefined
} = credentials
if ((!!email && !!username) || (!email && !username)) {
throw new Error('Should provide either email or username.')
}
if (!password) {
throw new Error('Password is required.')
}
const user = await User.findOne(email ? { email } : { username })
if (!user) {
throw new Error('Credentials are invalid!')
}
if (!bcrypt.compare(password, user.password)) {
throw new Error('Credentials are invalid!')
}
return user
}
So I am using this function to verify if the user provided valid credentials, and from my handler, I call the function on the model class.
Expect both username and password within your authentication middleware and then proceed with whatever value you have found as the condition to find the user.
Middleware example:
function authenticateUser(req, res, done) {
let username = req.body.username,
password = req.body.password,
email = req.body.email;
let conditions = !!username ? {username: username} : {email: email};
UserModel.findOne(conditions, (err, user) => {
if (err) return done(err);
if (!user) return done(new Error('Incorrect username or email'));
return user.comparePassword(password, user.password)
.then(match => {
if (match) return done();
else return done(new Error('Incorrect password'));
})
.catch(error => {
if (error) return done(new Error(`Unable to validated password. - ${error}`));
});
});
}
Now, a front-end developer — with the right documentation — can now actually use either the username, email or both (you will need a bit of JavaScript for both) when building login forms using your endpoint. Here is an example of using both:
HTML:
<form id="login-form" method="POST" action="/login">
<input id="username-or-email" type="text" placeholder="Username or Email" required/>
<input type="password" name="password" placeholder="Password" required/>
<input type="submit"/>
</form>
JavaScript:
// select the form element
let loginForm = document.querySelector('#login-form');
// add a form handler on submit
loginForm.addEventListener("submit", formHandler);
// validate and the set name attribute as appropriate
function formHandler() {
/** W3C Email regex: (RFC5322) */
const email_regex = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+#[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
/** Must starts with a letter then can include underscores (_) & hyphens (-) */
const username_regex = /^[a-zA-Z][\w-]+$/;
let input = document.querySelector('#username-or-email');
if (email_regex.test(input.value)) {
// it is an email, send the value as an email
input.setAttribute("name", "email");
} else if (username_regex.test(input.value)) {
// it is a username, send the value as a username
input.setAttribute("name", "username");
} else {
// invalid email or username format, return an error message to user
}
}
So you get to validate and set your dynamic input at the same time. Keep in mind that the regular expression should match your username and email data model as much as possible.
You can use getUserById inside the callback of getUserByEmail so that both of the queries run for either email or username. If you specify email, then emailOrUserName will have the email value and then User.getUserByEmail will return the user and next it will proceed with comparePassword.
And if you have username then emailOrUserName will have the username value and then User.getUserByEmail will not return the user so, it executes User.getUserById and if the user is found there then it will proceed with User.comparePassword else it will return Unknown user
passport.use(new LocalStrategy(
function(emailOrUserName, password, done) {
User.getUserByEmail(emailOrUserName, function(err, user, next){
if(err) throw err;
if(!user){
User.getUserById(emailOrUserName, function(err, user, next){
if(err) throw err;
if(!user){
return done(null, false, {message: 'Unknown user'});
}
}
User.comparePassword(password, user.password, function(err, isMatch){
if(err) throw err;
if(isMatch){
return done(null, user);
} else {
return done(null, false, {message: 'Invalid password'});
}
});
});
}));
Do this in local strategy
function(username, password, done) {
var criteria = (username.indexOf('#') === -1) ? {username: username} : {email: username};
User.findOne(criteria, function (err, user) { //implementation }
The code below can be used to allow your users to log in using either their username or email and password:
if (!req.body.username && !req.body.email) {
res.status(400).send({ message: "All input is required, username/email is missing",
status: false });
return;
}
if (!req.body.password) {
res.status(400).send({ message: "All input is required, password is missing",
status: false });
return;
}
let whereClause = {};
if (req.body.email) {
whereClause.email = req.body.email;
} else if (req.body.username) {
whereClause.username = req.body.username.toLowerCase();
}
const user = await User.findOne({ where: whereClause });
// If user not found
if (!user) {
return res.status(404).send({ message: "User not found", status: false });
}
You can wrap it in a try-catch block for proper error handling.
I add little more on the above answer shared by user7153178: Mine is related to the backend, using context.
Inside your Login component in the frontend, try to have these.
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
then you will be needing this function on your form:
const handleSubmit = () => {
const user = {
email,
password,
};
if (email === "" || password === "") {
setError("Please fill in your credentials");
} else {
loginUser(user, context.dispatch);
}
};
this is one of the input as part of your form
<Input
placeholder={"Email or Phone Number"}
name={"email"}
id={"email"}
value={email}
onChangeText={(text) =>
setEmail(text.toLowerCase())}
/>
In the backend write your post like that:
router.post("/login", async (req, res) => {
const user = await User.findOne(
{ $or: [{ email: req.body.email }, { phone:
req.body.email }] }
);
if (!user) {
return res.status(400).send("The user not found");
}
if (user && bcrypt.compareSync(req.body.password,
user.passwordHash)) {
const token = jwt.sign(
{
userId: user.id,
isAdmin: user.isAdmin,
},
secret,
{ expiresIn: "1d" }
);
res.status(200).send({ user: user.email, token:
token });
} else {
res.status(400).send("password is wrong!");
}
});