I am writing a application that uses JWTs for user authentication. I am trying to allow the user to submit or edit resources associated ONLY to there ID which is generated when they create their account in MongoDB only after the user has logged in. My instructor suggested to store the user._id within a cookie but not familiar with that process. Any tips on how I could accomplish this?
Here is an example of how my users are registered within the server
app.get('/register', function (req, res) {
res.sendFile(__dirname + '/public/register.html');
});
app.post('/register', async function (req,res) {
try {
req.body.password = await bcrypt.hash(req.body.password, 10);
var newUser = new User(req.body);
newUser.save(function(error,user){
res.redirect('/login');
});
} catch{
res.redirect('/register');
}
// console.log(newUser);
});
Here is the auth process
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const bcrypt = require('bcrypt');
const opts = {}
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = process.env.secret;
module.exports = new JwtStrategy(opts, async (jwt_payload,done)=> {
const user = await (req.body.email);
if( jwt_payload.email === req.body.email){
return done(null, true)
}
try {
if (await bcrypt.compare(password, user.password)){
return done(null, user);
} else {
return done(null, false, { message: "Password is incorrect"});
}
} catch (e) {
return done(e);
}
})
Here is logging in
app.post('/login', async (req, res) => {
const { username, password } = req.body
const user = await User.findOne({ username }).lean()
if (!user) {
return res.sendFile(__dirname + '/public/401.html');
}
if (await bcrypt.compare(password, user.password)) {
// the username, password combination is successful
const token = jwt.sign(
{
id: user._id,
username: user.username
},
process.env.secret
)
console.log(user._id);
//I need to find a way to send over the user._id to the client side so it can be stored later to allow the user to only update their resources and to view their resources
return res.redirect('/welcome.html');
}
res.sendFile(__dirname + '/public/401.html');
})
EDIT: I am not not wanting to store the token unless that is the only way to pass the user._id to the client side due to security reasoning.
Technologies I am using are, Node.js, Express.js, Mongoose, MongoDB, JWT, passport, jwt-simple, bcrypt, methodOverride, and secrets for resetting the users password and creating the user are stored in an .env.
Related
Currently my project takes sign up requests correctly, and stores emails and encrypted passwords into my MongoDB database. However, when I take the same e-mail and password I signed up with, I get an error telling me that my password is incorrect
I have the following sign in request set up:
module.exports.login_post = async (req, res) => {
// shows us the data that was sent.
// console.log(req.body)
// destructure to just get the email and password from the body
const { email, password } = req.body;
console.log('login called')
try {
const user = await User.login(email, password);
// creates and sends a jwt cookie
const token = createToken(user._id);
res.cookie("jwt", token, { httpOnly: true, maxAge: maxAge * 1000 });
res.status(200).json({ user: user._id });
} catch (err) {
const errors = handleErrors(err);
res.status(400).json({ errors });
}
console.log(email, password)
res.send('user login')
};
In this code I reference User.login, which is imported from my User Model
userSchema.statics.login = async function (email, password){
const user = await this.findOne({ email });
if(user){
const auth = await bcrypt.compare(password, user.password)
if(auth){
return user
}
throw Error('incorrect password')
}
throw Error('incorrect email')
}
Right now, I can register a user, but when I turn around and use the same login and password in Postman I get the error "incorrect password " from my User.login function.
At this point, I am not sure what steps I should be taking in problem solving this. Is there a way to console.log the encyrpted version of the password I try to log in with, so I can make sure I have Bcrypt set up to correctly encrypt the user's password?
Edit: My signup code was requested:
module.exports.signup_post = async (req, res) => {
// destructure to just get the email and password from the body
const { eMail, password } = req.body;
// console.log(email, password)
// add a try catch block
try {
const user = await User.create({
eMail,
password,
words: [
]
});
const token = createToken(user._id);
res.cookie("jwt", token, { httpOnly: true, maxAge: maxAge * 1000 });
// (max age is in seconds and this take milliseconds)
res.status(201).json({ user: user._id });
} catch (err) {
console.log(err)
const errors = handleErrors(err);
// console.log(err);
res.status(400).json({ errors });
}
res.send('new signup')
};
2nd Edit: and this is my middleware the fires before I save a new user and encrypts the password
// hash passwords using SALT
userSchema.pre('save', async function (next){
const salt = await bcrypt.genSalt()
this.password = await bcrypt.hash(this.password, salt)
console.log(this.password)
next()
})
Use below code as your password compare you chnage saltRounds with your bcrypt salt rounds it will compare it with password in database
const comparePassword = (hashedPassword, password) => {
return bcrypt.compareSync(password, hashedPassword);
};
let validations = {
comparePassword,
}
module.exports = validations;
I am trying to set up Passport with Express and MongoDB. At the moment I am able to register users in the database. But whenever I try to login, I get an error saying that data and hash arguments are required. Right now I have my Server.js file like this
const mongoose = require('mongoose');
const User = require('./models/users')
const initializePassport = require('./passport-config')
initializePassport(
passport,
email => User.find({email: email}),
id => User.find({id: id})
)
app.post('/register', checkNotAuthenticated, async (req, res) => {
try {
const hashedPassword = await bcrypt.hash(req.body.password, 10)
const newUser = new User({
id: Date.now().toString(),
name: req.body.name,
email: req.body.email,
password: hashedPassword
})
res.redirect('/login')
console.log(newUser)
} catch {
res.redirect('/register')
}
And my Passport-Config.js file like this `
const LocalStrategy = require('passport-local').Strategy
const bcrypt = require('bcrypt');
const User = require('./models/users')
function initialize(passport, getUserByEmail, getUserById) {
const authenticateUser = async (email, password, done) => {
const user = getUserByEmail(email)
if (user === null) {
return done(null, false, { message: 'No user with that email' })
}
try {
if (await bcrypt.compare(password, user.password)) {
return done(null, user)
} else {
return done(null, false, { message: 'Password incorrect' })
}
} catch (e) {
return done(e)
}
}
passport.use(new LocalStrategy({ usernameField: 'email' }, authenticateUser))
passport.serializeUser((user, done) => done(null, user.id))
passport.deserializeUser((id, done) => {
return done(null, User.findById({user: id}))
})
}
`
I've done some investigation using console.log() statements (not proud of it) but I think I've managed to find out the issue. If we add in the the first console log statement here:
app.post('/register', checkNotAuthenticated, async (req, res) => {
try {
console.log("BCRYPT COMPARE RUNS HERE")
const hashedPassword = await bcrypt.hash(req.body.password, 10)
const newUser = new User({
id: Date.now().toString(),
name: req.body.name,
email: req.body.email,
password: hashedPassword
})
res.redirect('/login')
console.log(newUser)
} catch {
res.redirect('/register')
}
and the second one here:
const initializePassport = require('./passport-config')
initializePassport(
passport,
email => User.find({email: email}).then((result) => { console.log("USER DATA EXTRACTED HERE") }).catch((err) => { console.log(err) }),
id => User.find({id: id})
)
The next time you click on login, you should see an output like:
Listening on port 3000
BCRYPT COMPARE HAPPENING
Error: data and hash arguments required
...
...
...
USER DATA EXTRACTED HERE
Notice that bcrypt.compare is being run before we are actually able to grab the user information from the DB? This means that all the arguments into that function are null, which is what is returning that error. Now, I'm no JS expert, but this can be fixed with an await statement added here:
function initialize(passport, getUserByEmail, getUserById) {
const authenticateUser = async (email, password, done) => {
const user = await getUserByEmail(email)
if (user === null) {
return done(null, false, { message: 'No user with that email' })
}
Which makes sure that the user info is queried from the DB before moving along in the script.
I am using express & jwt-simple to handle login/register & authenticated requests as a middleware api. I'm trying to create a .well-known endpoint so other api's can authenticate request based on token send in.
Here's my strategy:
module.exports = function() {
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeader();
opts.secretOrKey = securityConfig.jwtSecret;
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
// User.where('id', jwt_payload.id).fetch({withRelated: 'roles'})
console.log('jwt_payload', jwt_payload)
User.where('id', jwt_payload.id).fetch()
.then(user => user ? done(null, user) : done(null, false))
.catch(err => done(err, false));
}));
};
Here's my login route:
router.post('/login', function(req, res) {
const {username, password} = req.body;
Promise.coroutine(function* () {
const user = yield User.where('username', username).fetch();
if(user) {
const isValidPassword = yield user.validPassword(password);
if (isValidPassword) {
let expires = (Date.now() / 1000) + 60 * 30
let nbf = Date.now() / 1000
const validatedUser = user.omit('password');
// TODO: Verify that the encoding is legit..
// const token = jwt.encode(user.omit('password'), securityConfig.jwtSecret);
const token = jwt.encode({ nbf: nbf, exp: expires, id: validatedUser.id, orgId: validatedUser.orgId }, securityConfig.jwtSecret)
res.json({success: true, token: `JWT ${token}`, expires_in: expires});
} else {
res.status(401);
res.json({success: false, msg: 'Authentication failed'});
}
} else {
res.status(401);
res.json({success: false, msg: 'Authentication failed'});
}
})().catch(err => console.log(err));
});
Here's my .well-known route:
router.get('/.well-known', jwtAuth, function(req, res) {
// TODO: look over res.req.user. Don't seem to be the way to get those parameters.
// We dont take those parameters from the decrypted JWT, we seem to grab it from the user in DB.
const { id, orgId } = res.req.user.attributes;
console.log("DEBUG: userId", id)
console.log("DEBUG: USER", res.req.user)
res.json({
success: true,
userId: id,
orgId
});
});
here's my jwtAuth() function:
const passport = require('passport');
module.exports = passport.authenticate('jwt', { session: false });
How would I actually get the token in the route function & decrypt it? All this does right now which works is that it authenticates if true however I need to be able to decrypt the token to send back the stored values. I'm not sure what res.req.user.attributes comes from, is this the token?
Take a look at passport-jwt and in your passport-config (or wherever you initialize passport) setup JWT Strategy:
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const jwtAuth = (payload, done) => {
const user = //....find User in DB, fetch roles, additional data or whatever
// do whatever with decoded payload and call done
// if everything is OK, call
done(null, user);
//whatever you pass back as "user" object will be available in route handler as req.user
//if your user does not authenticate or anything call
done(null, false);
}
const apiJwtOptions: any = {};
apiJwtOptions.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
apiJwtOptions.algorithms = [your.jwt.alg];
apiJwtOptions.secretOrKey = your.jwt.secret;
//apiJwtOptions.issuer = ???;
//apiJwtOptions.audience = ???;
passport.use('jwt-api', new JwtStrategy(apiJwtOptions, jwtAuth));
If you want just decoded token, call done(null, payload) in jwtAuth.
Then in your route files when you want to protect endpoints and have info about user, use as:
const router = express.Router();
router.use(passport.authenticate('jwt-api', {session: false}));
And in handler you should have req.user available. It is configurable to what property of req you store data from auth, req.user is just default.
I used passport-jwt for login. it find the user and the user can login but when after logging in, I want to redirect to the user dashboard nothing happens and it shows Unauthorized the in browser when it redirect to localhost:8080/my/
can you find out what the problem is:
p.s: when I remove passport.authenticate('jwt', {session: false}) from my.js it works and it redirects to /my/. so I think the problem is with this part of code or JWTStrategy in auth.js. I don't know how to fix it!
here's my code : (sorry it's to much code)
auth.js:
//Create a passport middleware to handle User login
passport.use(new LocalStrategy({
usernameField: 'username',
passwordField: 'password'
}, async (username, password, done) => {
try {
//Find the user associated with the email provided by the user
await User.findOne({
username: username
}, async function (err, user) {
if (!user) {
//If the user isn't found in the database, return a message
return done(null, false, {
message: 'User not found'
});
}
//Validate password and make sure it matches with the corresponding hash stored in the database
//If the passwords match, it returns a value of true.
let validate = await user.isValidPassword(password);
if (!validate) {
return done(null, false, {
message: 'Wrong Password'
});
}
//Send the user information to the next middleware
return done(null, user, {
message: 'Logged in Successfully'
});
});
} catch (error) {
return done(error);
}
}));
passport.use(new JWTStrategy({
jwtFromRequest : ExtractJWT.fromAuthHeaderAsBearerToken(),
secretOrKey : 'secret_token'
}, function(jwt_payload, done) {
User.findOne({id: jwt_payload.sub}, function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
return done(null, user);
} else {
return done(null, false);
// or you could create a new account
}
});
}));
authenticate.js :
router.post('/login', async (req, res, next) => {
passport.authenticate('local', async (err, user, info) => {
try {
if (err || !user) {
const error = new Error('An Error occured')
return next(error);
}
req.login(user, {
session: false
}, async (error) => {
if (error) return next(error)
//We don't want to store the sensitive information such as the
//user password in the token so we pick only the email and id
const body = {
_id: user._id,
username: user.username
};
//Sign the JWT token and populate the payload with the user email and id
const token = jwt.sign({
user: body
}, 'top_secret');
//Send back the token to the user
// return res.json({
// token
// });
res.redirect('/my/?token='+token);
});
} catch (error) {
return next(error);
}
})(req, res, next);
});
module.exports = router;
user.js
const mongoose = require('mongoose');
const Role = require('./role');
const bcrypt = require('bcrypt');
let Schema = mongoose.Schema;
let userSchema = new Schema({
.
.
.
});
userSchema.pre('save', async function(next){
//'this' refers to the current document about to be saved
const user = this;
//Hash the password with a salt round of 10, the higher the rounds the more secure, but the slower
//your application becomes.
const hash = await bcrypt.hash(this.password, 10);
//Replace the plain text password with the hash and then store it
this.password = hash;
//Indicates we're done and moves on to the next middleware
next();
});
userSchema.methods.isValidPassword = async function(password){
const user = this;
//Hashes the password sent by the user for login and checks if the hashed password stored in the
//database matches the one sent. Returns true if it does else false.
let compare= await bcrypt.compare(password , user.password);
return compare;
}
module.exports = mongoose.model('User' , userSchema);
my.js
//secure routes that only users with verified tokens can access.
//Lets say the route below is very sensitive and we want only authorized users to have access
//Displays information tailored according to the logged in user
router.get('/',passport.authenticate('jwt', {session: false}), (req, res) => {
//We'll just send back the user details and the token
res.json({
message : 'You made it to the secure route',
token : req.query.token
});
})
module.exports = router;
This problem usually occurs because of an invalid JWT. To authenticate a JWT with passport-jwt it has to be of the following format:
"JWT xxxx.xxxx.xxxx"
So maybe try changing the following line in your Authenticate.js file:
res.redirect('/my/?token='+token);
// CHANGE IT TO:
res.redirect('/my/?token=' + 'JWT ' + token);
I hope this solves your problem.
I'm using Passport for authentication, specifically with a JWT strategy. I'm able to create a new token when a user is created, however, when I use that token in the header of a request to a route that requires authentication, my request just hangs up. I'm using Postman to test these POST/GET requests.
Here's my initial configuration for signing up a user:
const User = require('../db/models/User');
const jwt = require('jsonwebtoken');
function userToken(user) {
return jwt.sign({
id: user.id,
}, process.env.JWT_SECRET);
}
exports.signup = function(req, res, next) {
const email = req.body.email.toLowerCase();
const password = req.body.password.toLowerCase();
User.findOne({
where: { email },
}).then(function(user) {
if (!user) {
User.create({
email,
password,
})
.then(function(user) {
return res.send({ token: userToken(user) });
});
}
if (user) {
return res.send({ message: 'That user is in use' });
}
});
};
Here's my passport configuration:
const passport = require('passport');
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const User = require('../db/models/User');
const jwtOptions = {
jwtFromRequest: ExtractJwt.fromHeader('authorization'),
secretOrKey: process.env.JWT_SECRET,
};
const jwtLogin = new JwtStrategy(jwtOptions, function(payload, done) {
User.findOne({
where: { id: payload.id },
}, function(err, user) {
if (err) { return done(err, false); }
if (user) { return done(null, user); }
return done(null, false);
});
});
passport.use(jwtLogin);
Here's what my protected route looks like:
const passport = require('passport');
const requireAuth = passport.authenticate('jwt', { session: false });
module.exports = function router(app) {
app.get('/', requireAuth, function(req, res) {
res.send({ 'hi': 'there' });
});
};
Here's what I see in my terminal:
Executing (default): SELECT "id", "username", "email", "password", "photo", "createdAt", "updatedAt" FROM "users" AS "user" WHERE "user"."id" = 15;
So I know that it's correctly querying for a user id and searching for it, however, it just hangs up at this point, rather than serving me a response.
Not sure what the issue is, so any and all suggestions are welcomed and appreciated. Thank you!
Realized that because I am using Sequelize, it handles errors with a catch like so:
...
const jwtLogin = new JwtStrategy(jwtOptions, function(payload, done) {
User.findOne({
where: { id: payload.id }
})
.then(user => {
if (user) {
done(null, user);
} else {
done(null, false);
}
})
.catch(err => {
if (err) { return done(err, false); }
});
});
...
This solved my issue and is returning my response.