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.'
});
}
});
})
}
Related
I am trying to check whether the user tried to sign-In is admin or not. If isAdmin = true than admin, if isAdmin = false than not. But, when I am trying to export my admin function in my authMiddleware.js I am getting following error
Everything was working fine before I added admin function and tried to export it in my authMiddleware.js. Here is the code:
authMiddleware.js
const jwt = require("jsonwebtoken");
const User = require("../models/userModel");
exports.protect = async (req, res, next) => {
let token;
if (
req.headers.authorization &&
req.headers.authorization.startsWith("Bearer")
) {
try {
token = req.headers.authorization.split(" ")[1];
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = await User.findById(decoded.id).select("-password");
next();
} catch (error) {
error = new Error("Not Authorized!!");
error.status = 401;
next(error);
}
}
if (!token) {
const error = new Error("Not Authorized!!, No Token!!");
error.status = 401;
next(error);
}
};
exports.admin = (req, res, next) => {
if (req.user && req.user.isAdmin) {
next();
} else {
const error = new Error("Not Authorized As An Admin");
error.status = 401;
next(error);
}
};
userRoutes.js
const express = require("express");
const {
authUser,
getUserProfile,
registerUser,
updateUserProfile,
getUsers,
} = require("../controllers/userController");
const { protect, admin } = require("../middleware/authMiddleware");
const router = express.Router();
router.route("/").post(registerUser).get(protect, admin, getUsers);
router.post("/login", authUser);
router
.route("/profile")
.get(protect, getUserProfile)
.put(protect, updateUserProfile);
module.exports = router;
userController.js
const User = require("../models/userModel");
const generateToken = require("../utils/generateToken");
// #description: Auth user & Get token
// #route: POST /api/users/login
// #access: Public
exports.authUser = async (req, res, next) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (user && (await user.matchPassword(password))) {
res.json({
_id: user._id,
name: user.name,
email: user.email,
isAdmin: user.isAdmin,
token: generateToken(user._id),
});
} else {
const error = new Error("Invalid email or password");
error.status = 401;
next(error);
}
} catch (error) {
console.log("_id: ", user._id);
error = new Error("Invalid user data");
error.status = 401;
next(error);
}
};
// #description: Register a new User
// #route: POST /api/users/users
// #access: Public
exports.registerUser = async (req, res, next) => {
try {
const { name, email, password } = req.body;
const userExists = await User.findOne({ email });
if (userExists) {
const error = new Error("User already exists");
error.status = 400;
next(error);
}
const user = await User.create({
name,
email,
password,
});
if (user) {
res.status(201).json({
_id: user._id,
name: user.name,
email: user.email,
isAdmin: user.isAdmin,
token: generateToken(user._id),
});
}
} catch (error) {
error.status = 400;
next(error);
}
};
// #description: Get user profile
// #route: GET /api/users/profile
// #access: Private
exports.getUserProfile = async (req, res, next) => {
try {
const user = await User.findById(req.user._id);
if (user) {
res.json({
_id: user._id,
name: user.name,
email: user.email,
isAdmin: user.isAdmin,
});
}
} catch (error) {
error = new Error("User not found");
error.status = 404;
next(error);
}
};
// #description: Update user profile
// #route: PUT /api/users/profile
// #access: Private
exports.updateUserProfile = async (req, res, next) => {
try {
const user = await User.findById(req.user._id);
if (user) {
user.name = req.body.name || user.name;
user.email = req.body.email || user.email;
if (req.body.password) {
user.password = req.body.password;
}
const updatedUser = await user.save();
res.json({
_id: updatedUser._id,
name: updatedUser.name,
email: updatedUser.email,
isAdmin: updatedUser.isAdmin,
token: generateToken(updatedUser._id),
});
}
} catch (error) {
error = new Error("User not found");
error.status = 404;
next(error);
}
};
// #description: Get all users
// #route: GET /api/users
// #access: Private/Admin
exports.getUsers = async (req, res, next) => {
try {
const user = await User.find({});
res.json(user);
} catch (error) {
error = new Error("cant find any user's profile");
error.status = 404;
next(error);
}
};
// #description: Delete users
// #route: DELETE /api/users/:id
// #access: Private/Admin
exports.deleteUsers = async (req, res, next) => {
try {
const user = await User.findById(req.params._id);
if (user) {
await user.remove();
res.json({ message: "User Removed" });
}
} catch (error) {
error = new Error("cant find any user's profile");
error.status = 404;
next(error);
}
};
I have a DELETE operation as part of a NodeJS API I have built. This delete should do the following:
Delete a player object with the ID provided
It should not delete a player created by another user.
In the tests it passes if the player is created by another user but fails when it attempts to delete the object. Here is the code:
router.delete('/:id', validateBearerToken, function(req, res) {
let playerId = req.params.id;
//get player object
let player = Player.find({created_by: playerId
}, function(err) {
if (err) return res.status(409).send('There was a problem finding the players.');
});
if (player.created_by !== getUserFromBearerToken(req.token)) {
return res.status(404).send('The player not created by this user');
}
Player.findByIdAndRemove(playerId, function(err) {
if (err) {
return res.status(404).send('There was a problem deleting the player.');
}
res.status(200).send({
success: true
});
});
});
validateBearerToken is used to check if the user executing the delete operation is valid
function validateBearerToken(req, res, next) {
let bearerToken;
let bearerHeader = req.headers.authorization;
if (typeof bearerHeader !== 'undefined') {
let bearer = bearerHeader.split('Bearer ');
bearerToken = bearer[1];
req.token = bearerToken;
next();
} else {
res.status(403).send();
}
}
gertUserFromBearerToken is used to get the id of the logged in user to compare with the 'created_by` id in the test:
function getUserFromBearerToken(token) {
const decodedtoken = jwt.decode(token, process.env.JWT_SECRET);
return decodedtoken.id;
}
you verification middle-ware (validateBearerToken) should be like this
function validateBearerToken(req, res, next) {
var token = req.headers.authorization || req.headers['x-access-token'];
if (!token)
return res.status(403).send({ auth: false, message: 'No token provided.' });
jwt.verify(token, process.env.JWT_SECRET, function(err, decoded) {
if (err)
return res.status(500).send({ auth: false, message: 'Failed to authenticate token.' });
// if everything good, save to request for use in other routes
req.userId = decoded.id;
next();
});
}
then in delete route check for id like this
if (player.created_by !== req.userId) {
return res.status(404).send('The player not created by this user');
}
I want to validate my registration page and i am using Joi validation every thing working fine except google recaptcha.
Here is code for form validation snippet,
var UserSchema = Joi.object().keys({
name: Joi.string().required(),
username: Joi.string().alphanum().min(4).max(30).required(),
email: Joi.string().email().required(),
mobile: Joi.string().required(),
password: Joi.string().regex(/^[a-zA-Z0-9]{8,30}$/),
confirmationPassword: Joi.any().valid(Joi.ref('password')).required(),
access_token: [Joi.string(), Joi.number()],
recaptcha : Joi.string()
});
Post request for registration page,
router.route('/register')
.get(isNotAuthenticated, (req, res) => {
res.render('register');
})
.post(async (req, res, next) => {
try {
const data = Joi.validate(req.body, UserSchema);
if (data.error) {
req.flash("error", "Enter Valid data");
console.log(data.error);
res.redirect('/register');
return;
}
const user = await User.findOne({ username: data.value.username });
if (user) {
req.flash('error', 'Username already taken');
res.redirect('/register');
return;
}
if (req.body.captcha === undefined ||
req.body.captcha === '' ||
req.body.captcha === null) {
res.redirect('/register');
return;
}
const secretKey = '';
const verifyUrl = `https://google.com/recaptcha/api/siteverify?secret=${secretKey}&response=${req.body.captcha}&remoteip=${req.connection.remoteAddress}`;
request(verifyUrl, async (err, response, body) => {
try{
body = JSON.parse(body);
console.log(body);
if (body.success !== undefined && !body.success) {
res.redirect('/register');
return;
} else {
const newUser = await new User(data.value);
console.log('newUser', newUser);
await newUser.save();
}
}catch(e){
throw e;
}
});
req.flash('success', 'Please check your email.');
res.redirect('/login');
} catch (error) {
next(error);
}
});
When i click on submit i got error message in console that { ValidationError: "g-recaptcha-response" is not allowed
You could access g-recaptcha-response using bracket notation. So in your Joi schema the property should look something like this:
["g-recaptcha-response"]: Joi.string().required();
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
I just looking for solution which makes verification email with token for my local autentification in passport.js
Is there some plugin or component for node which can make me verification easyer? Or I have to do it myself?
My controller
exports.postSignup = function(req, res, next) {
req.assert('email', 'Email is not valid').isEmail();
req.assert('password', 'Password must be at least 4 characters long').len(4);
req.assert('confirmPassword', 'Passwords do not match').equals(req.body.password);
var errors = req.validationErrors();
if (errors) {
req.flash('errors', errors);
return res.redirect('/signup');
}
var user = User.build({
email: req.body.email,
password: req.body.password,
});
User
.find({ where: { email: req.body.email } })
.then(function(existingUser){
if (existingUser) {
req.flash('errors', { msg: 'Account with that email address already exists.' });
return res.redirect('/signup');
}
user
.save()
.complete(function(err){
if (err) return next(err);
req.logIn(user, function(err){
if (err) return next(err);
res.redirect('/');
});
});
}).catch(function(err){
return next(err);
});
};
Thanks for any opinion!
Implementing this yourself is pretty straightforward.
The pseudocode:
//A user registers
//User is stored along with a random token string and a variable set to false
//User is sent a verification email
//Verification email has a link with the random token and a unique ID for that user
//Link goes to a route that takes the token as a parameter
//Match the user and the random token
//If they match - change a variable to verified
The package I use to generage the random string is:
https://www.npmjs.com/package/randomstring
Local signup strategy
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true // allows us to pass back the entire request to the callback
},
function (req, email, password, done) {
// asynchronous
// User.findOne wont fire unless data is sent back
process.nextTick(function () {
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.findOne({'local.email': email}, function (err, user) {
// if there are any errors, return the error
if (err) {
return done(err);
}
// check to see if theres already a user with that email
if (user) {
console.log('that email exists');
return done(null, false, req.flash('signupMessage', email + ' is already in use. '));
} else {
User.findOne({'local.username': req.body.username}, function (err, user) {
if (user) {
console.log('That username exists');
return done(null, false, req.flash('signupMessage', 'That username is already taken.'));
}
if (req.body.password != req.body.confirm_password) {
console.log('Passwords do not match');
return done(null, false, req.flash('signupMessage', 'Your passwords do not match'));
}
else {
// create the user
var newUser = new User();
var permalink = req.body.username.toLowerCase().replace(' ', '').replace(/[^\w\s]/gi, '').trim();
var verification_token = randomstring.generate({
length: 64
});
newUser.local.email = email;
newUser.local.password = newUser.generateHash(password);
newUser.local.permalink = permalink;
//Verified will get turned to true when they verify email address
newUser.local.verified = false;
newUser.local.verify_token = verification_token;
try {
newUser.save(function (err) {
if (err) {
throw err;
} else {
VerifyEmail.sendverification(email, verification_token, permalink);
return done(null, newUser);
}
});
} catch (err) {
}
}
});
}
});
});
}));
I use a combination of /permalink/random-token for the verification URL
The route should look like this:
app.get('/verify/:permaink/:token', function (req, res) {
var permalink = req.params.permaink;
var token = req.params.token;
User.findOne({'local.permalink': permalink}, function (err, user) {
if (user.local.verify_token == token) {
console.log('that token is correct! Verify the user');
User.findOneAndUpdate({'local.permalink': permalink}, {'local.verified': true}, function (err, resp) {
console.log('The user has been verified!');
});
res.redirect('/login');
} else {
console.log('The token is wrong! Reject the user. token should be: ' + user.local.verify_token);
}
});
});