I am trying to create a passport remember me strategy but I am not sure how to call it. My overall strategy is to store two tokens in my database and as cookies on the client's computer and compare then to verify that they are real users. I am currently attempting to pass app.use a passport.authenticate strategy so that I can verify success of failure using my strategy.
In my app.js file I have:
passport.use('rememberMe',new passportLocal.Strategy({ passReqToCallback: true },
(req, cb) => {
//check req.cookies['token']...
return cb(null, (rememberMe.checkPersistance(req.cookies['token'], req.cookies['statictoken'])));
}));
app.use((req, res) => passport.authenticate('rememberMe'), (req, res) => {
//successfully logged in!
})
Note: rememberMe.checkPersistance does the comparison against the database and returns a true or false.
My problem is that I don't think I am using the app.use syntax correctly and I am not sure what the correct way to do it. How do I use passport.authenticate when it isn't in a .POST function?
I figured out the answer to this question and overall I only had this problem because I didn't understand how .get and .post worked. For both each function you pass it, the function can pick up request, response, and next.
So you can replace .post with .get for most examples of passport you will see online. The difference between them will be what is post is designed to be sent data and then return something (like login information) while get is designed to be a way to query some information. Here is more detailed explanation.
Create a Schema for Tokens
'use strict'
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
const TokenSchema = Schema({
value: {
type: String,
required: true
},
user: {
type: Schema.Types.ObjectId,
ref: 'users',
required: true
}
});
module.exports = mongoose.model('token', TokenSchema);
Then define your strategie
passport.use(new RememberMeStrategy(
function(token, done) {
Token.findOneAndRemove({ value: token })
.populate('user')
.exec( function (err, doc) {
if(err) return done(err);
if(!doc) return done(null,false);
return done(null, doc.user);
});
},
function(user, done) {
crypto.randomBytes(64, (err, buf) => {
const value = buf.toString('hex');
const token = new Token({
value: value,
user: user._id
});
token.save((err) => {
if (err) return done(err);
console.log(value);
return done(null, value)
});
});
}
));
I have found an issue : i can't logged out after define this strategie and check the remember me box.
I just want the form loggin to be autofilled when i come back but it seems this module is useless, it not have the behaviour I want.
Related
I've have had a working version of mongoose instance methods working before. I'm not sure what is different about it this time around. The only thing I have done differently this time is that I separated the mongoose connection function outside of the server.js file into a config file that will be imported and call the connect() function.
I will mostly use this instance method in passport with the local strategy to log in the user. When I go to call my instance method on the user instance that was found by the previous UserModel.findOne({ email }) the verify(password) instance method is not called and does not throw any errors.
For testing purposes, I've tried to hard code a UserModel.findOne() right into connection field and I do get a user back. I then decided to call my instance method right off of the returned user instance named verify().
I have also attempted changing the name of the method to comparePassword, I've tried testing with statics to check if it is even called at all (which it wasn't), I've also tried to research other ways to import my schemas and models, and that does not seem to work. I have experimented with Async/Await hasn't changed the output
File: mongo.db.js
const connect = () => {
return new Promise((resolve, reject) => {
mongoose.connect(
config.get('DB.STRING'),
{ useCreateIndex: true, useNewUrlParser: true },
async (err) => {
if (err) reject(err)
resolve()
// TESTING INSTANCE METHODS
await mongoose.connection
.collection('users')
// HARD CODED TEST EMAIL
.findOne({ email: 'braden_feeney#hotmail.com' }, (err, result) => {
if (err) reject(err)
console.log(result)
console.log(result.verify('test1234'))
})
},
)
})
}
const close = () => {
return mongoose.disconnect()
}
export default { connect, close }
File: passport.config.js
passport.use(
new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password',
},
async (email, password, done) => {
try {
// Find the user given the email
const user = await User.findOne({ email })
// If not found
if (!user) return done(null, false)
// Check if the password is correct
const isValididated = await user.verify(password)
// If not matched
if (!isValididated) return done(null, false)
// Return the user
done(null, user)
} catch (error) {
done(error, false)
}
},
),
)
File: users.model.js
const UserSchema = new Schema(
// HIDDEN FOR SECURITY
{ ... },
{ versionKey: false, timestamps: true },
)
// HIDDEN FOR SECURITY - PRE SAVE WORKS AS EXPECTED
UserSchema.pre('save', async function(next) { ... })
// THIS IS THE METHOD THAT SHOWS AS 'Not a Function'
UserSchema.methods.verify = function(password) {
bcrypt.compare(password, this.password, (err, res) => {
if (err) return new Error(err)
return res
})
}
export default model('User', UserSchema)
When I call user.verify(password) I expect to see a Boolean value either get returned from the function.
The actual result is an Error thrown stating
TypeError: user.verify is not a function
This section does not make sense:
async (email, password, done) => {
try {
const user = await User.findOne({ email })
if (user) // <-- if you have a user record
return done(null, false) // <-- you return!
// There was no valid user ... how would there be user.verify?
const isValididated = await user.verify(password)
if (!isValididated) return done(null, false)
done(null, user)
} catch (error) {
done(error, false)
}
}
You seem to return on valid user but calling user.verify when you do not have a user. So it seems your flow is somewhat problematic.
After tinkering around with how mongoose creates instance methods, I tried using another way to make the method work. I had to wrap the bcrypt.compare() in a promise because my code wasn't waiting for a response.
File: users.model.js
UserSchema.method('verify', function(password) {
return new Promise((resolve, reject) => {
bcrypt.compare(password, this.password, (err, res) => {
if (err) reject(err)
resolve(res)
})
})
})
I still prefer the way that is mentioned in my question because I believe it looks cleaner and is not relying on a string to be the method name.
All you have to do is just mark this function with "async".
// THIS IS THE METHOD THAT SHOWS AS 'Not a Function'
UserSchema.methods.verify = async function (password) {
bcrypt.compare(password, this.password, (err, res) => {
if (err) return new Error(err)
return res
})
}
& Another thing that you've to keep in mind is that when you're retrieving user from your DB make sure to use the "await" keyword there as well.
I was getting the same error and then I noticed that I forgot to use the "await" keyword and my code goes to the next line without even waiting for the "user" to arrive from DB and we don't have a user or instance and we're calling "compare" method on that instance which hasn't arrived from DB.
That's why you were getting error that comparePassword is not a function.
First of all this is NOT a duplicate since all the documentation/answers out there are for versions prior to v1.0 and they don't seem to work.
I'm trying to implement a simple authentication with passport and SailsJS v1.0.
The problem is...since i'm new to this and sailsjs (v1) seems to lack online examples, i'm pretty stuck.
The app should work like this -> User registers, validates their email and then logs in. Upon logging in the user gets back a accessToken which he needs to use to make requests to protected routes (via Bearer or something else).
The tokens should be saved in the DB so i can invalidate them when the user changes password and such.
How could i achieve something like this ? This is what i've got so far (merging older/newer examples online).
User.js (model)
const bcrypt = require('bcryptjs');
module.exports = {
attributes: {
email: {
type: 'string',
required: true,
unique: true
},
username: {
type: 'string',
required: true,
unique: true
},
password: {
type: 'string',
required: true
},
tokens: {
collection: 'token',
via: 'userId'
}
},
customToJSON: function () {
return _.omit(this, ['password'])
},
beforeCreate: function (user, cb) {
bcrypt.genSalt(10, function (err, salt) {
bcrypt.hash(user.password, salt, null, function (err, hash) {
if (err) return cb(err);
user.password = hash;
return cb();
});
});
}
};
Token.js (model)
module.exports = {
attributes: {
token: {
type: 'string',
required: true,
unique: true
},
userId: {
mode: 'user'
},
isValid: {
type: 'bool',
}
},
};
Since i'm very new to node and especially sails i've got quite a few questions.
How could i create a token on user login if the token in the DB is
invalid (or should i just use 1 token at registration/change
password ?)
How do i implement the actual authentication process
using passport ?
Should the token be a specific format (number of characters etc) for
security reasons ?
Is there a better approachto what i'm trying to achieve ? (to be
more exact i want a REST Api back-end to serve my ReactJS front-end
with the ability to reuse the same back-end for android/ios etc).
Any tips, links, suggestions etc will be highly appreciated. Thanks and have mercy on my lack of knowledge!
EDIT: Would this approach stop me from using social media login (i want to implement that in the future too)?
You can use JWT for authentication.
Create file called isAuthenticated.js in api/policies folder with following code.
const passport = require('passport');
module.exports = async (req, res, proceed) => {
passport.authenticate('jwt', { session: false }, (err, user, info) => {
if (err) {
res.serverError(err, err.message);
}
if (user) {
req.user = user;
return proceed();
}
if (info) {
return res.forbidden(info);
}
// Otherwise, this request did not come from a logged-in user.
return res.forbidden();
})(req, res, proceed);
};
Create strategies folder in api folder with jwt.js file and following code.
const JwtStrategy = require('passport-jwt').Strategy;
const { ExtractJwt } = require('passport-jwt');
const passport = require('passport');
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = JWT_SECRET;
opts.issuer = JWT_ISSUER;
opts.audience = JWT_AUDIENCE;
opts.jsonWebTokenOptions = {
expiresIn: JWT_EXPIRES_IN,
};
module.exports = {
/**
* Passport Strategy
*/
passport: () => {
passport.use(new JwtStrategy(opts, (jwtPayload, done) => {
User.findOne({ id: jwtPayload.id }, (err, user) => {
if (err) {
return done(err, null);
}
if (user) {
return done(null, user);
}
return done({ message: 'No user account found' }, 'No user account found');
});
}));
},
};
And in app.js require('./api/strategies/jwt').passport(); at top of the file.
Now you can apply policies to the routes in config/policies.js.
To generate JWT, you can use the following code.
const jwt = require('jsonwebtoken');
module.exports = {
generate: (id, email) => jwt.sign({ id, email }, jwtSecret, {
audience: jwtAudience,
expiresIn: jwtExpiresIn,
issuer: jwtIssuer,
})
};
I am getting in to NodeJS and have followed some video tutorials for making stuff, to understand NodeJS and Express. It turned more in to copying as little were explained, so tried to make my own thing using what I learned and so on.
Making a simple login function with PassportJS, ExpressJs and Mongoose.
The login and stuff works, and I can get the username of the currently logged in user and display it if I define it within the main app.js using this:
app.get("/stuff", (req,res) => {
res.render("stuff.html", {username:req.user.username});
});
Now if I want to make nice and structured by using router, I cannot get it to work. It throws error saying username is undefined, making page unable to render. The router itself works if I don't pass any variable or use variables I know will work (e.g. var x = "Hello"; res.render … {msg:x});).
Part of the app.js that handle routes:
var stuff = require("./routes/stuff");
app.use("/stuff", stuff);
module.exports.app;
I've tried to cont x = require("…") basically everything that is in the app.js in this stuff.js file, but to no avail, so removed everything but express + routes to get fresh start.
How do I pass the username that is working in app.js in to the routed file? Preferably make it automatically do to every page if possible, using app.get("*")… or something.
Entire stuff.js:
/* Routes */
const express = require("express");
const router = express.Router();
/* Stuff */
router.get("/", function(req, res, next) {
res.render("stuff.html", {username:req.user.username});
console.log(req.user.username);
next();
});
/* Bottom */
module.exports = router;
Login section of app.js:
app.post('/login',
passport.authenticate('local',
{
successRedirect: '/dashboard',
failureRedirect: '/login',
failureFlash: 'Wrong login'
}
), function(req,res) {
console.log("Hello " + req.user.username);
});
passport.serializeUser(function(user,done) {
done(null, user.id);
});
passport.deserializeUser(function(id,done) {
User.getUserById(id, function(err, user) {
done(err,user);
});
});
passport.use(new LocalStrategy(function(username,password,callback) {
User.getUserByUsername(username, function(err,user) {
if(err) throw err;
if(!user) {
return callback(null, false, {msg: "shit"});
}
User.comparePassword(password, user.password, function(err,isMatch) {
if(err) return callback(err);
if(isMatch) {
return callback(null, user);
} else {
return callback(null, false, {msg:"Something"});
}
});
});
}));
The users.js file for handling registering new users, if that's relevant:
const mongoose = require("mongoose");
mongoose.connect("mongodb://localhost/users");
const db = mongoose.connection;
mongoose.Promise = global.Promise;
const bcrypt = require("bcryptjs");
/* Data schema */
const userSchema = mongoose.Schema({
name: {
type: String
},
username: {
type: String,
index: true
},
password: {
type: String
},
email: {
type: String
}
});
var User = module.exports = mongoose.model("User", userSchema);
module.exports.createUser = function(newUser, callback) {
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(newUser.password, salt, function(err, hash) {
newUser.password = hash;
newUser.save(callback);
});
});
}
module.exports.getUserById = function(id, callback) {
User.findById(id, callback);
}
module.exports.getUserByUsername = function(username, callback) {
var query = {username: username};
User.findOne(query, callback);
}
module.exports.comparePassword = function(testPw, hash, callback) {
bcrypt.compare(testPw, hash, function(err,isMatch) {
callback(null,isMatch);
});
}
As far as I understand you are trying to pass your username to the preferably every file including your router file. What I do for this to use middleware in app.js to pass every page. Or you can simply implement passport implementation in the other page as well which could be useless i guess.
app.use(function(req,res,next){
res.locals.currentUser=req.user
next()
}
Then, you can use use your currentUser in every page when you try to render.
I encountered the same issue, probably after following the same tutorials...
I found that the function you need in the app.js is:
app.get('*', function(req, res,next){
res.locals.user = req.user || null;
next();
})
it should be in the app.js already. Now, in all of the other page you should be able to use req.user.username
I am attempting to use Passport Authentication on my web app. I am using Sequelize ORM, Reactjs front-end and express and node back end. Right now, when I register a user everything works fine. the problem comes when I try to login. I see the user querying the DB to find the user with correct email, but when it is time to compare passwords, i am catching an error.
"Unhandled rejection TypeError: dbUser.validPassword is not a function"
here is my config/passport.js file:
var passport = require("passport");
var LocalStrategy = require("passport-local").Strategy;
var db = require("../models");
// Telling passport we want to use a Local Strategy. In other words, we
want login with a username/email and password
passport.use(new LocalStrategy(
// Our user will sign in using an email, rather than a "username"
{
usernameField: "email"
},
function(email, password, done) {
// When a user tries to sign in this code runs
db.User.findOne({
where: {
email: email
}
}).then(function(dbUser) {
// If there's no user with the given email
if (!dbUser) {
return done(null, false, {
message: "Incorrect email."
});
}
// If there is a user with the given email, but the password the user
gives us is incorrect
else if (!dbUser.validPassword(password)) {
return done(null, false, {
message: "Incorrect password."
});
}
// If none of the above, return the user
return done(null, dbUser);
});
}
));
// In order to help keep authentication state across HTTP requests,
// Sequelize needs to serialize and deserialize the user
// Just consider this part boilerplate needed to make it all work
passport.serializeUser(function(user, cb) {
cb(null, user);
});
passport.deserializeUser(function(obj, cb) {
cb(null, obj);
});
// Exporting our configured passport
module.exports = passport;
Here is my User Model:
var bcrypt = require("bcrypt-nodejs");
[![enter image description here][1]][1]module.exports = function(sequelize, DataTypes){
var User = sequelize.define("User", {
email: {
type: DataTypes.STRING,
allowNull: false,
validate: {
isEmail: true
}
},
password: {
type: DataTypes.STRING,
allowNull: false
},
},{
classMethods: {
associate: function(models) {
User.hasOne(models.Educator, {
onDelete: "cascade"
});
User.hasOne(models.Expert, {
onDelete: "cascade"
});
}
},
instanceMethods: {
validPassword: function(password) {
return bcrypt.compareSync(password, this.password);
}
},
// Hooks are automatic methods that run during various phases of the User Model lifecycle
// In this case, before a User is created, we will automatically hash their password
hooks: {
beforeCreate: function(user, options) {
console.log(user, options )
user.password = bcrypt.hashSync(user.password, bcrypt.genSaltSync(10), null);
}
}
})
return User;
}
I am also including an image of the error.
As of sequelize version >4, it has changed the way instance methods are defined.
They follow a more class based approach now,
A sample from the Docs for how it has to be done
const Model = sequelize.define('Model', { ... });
// Class Method
Model.associate = function (models) { ...associate the models };
// Instance Method
Model.prototype.someMethod = function () {..}
The syntax you are using corresponds to sequelize < 4.
I have a very simple site that is using Passport JS to create local login strategy to hit a local postgres database using the Sequelize ORM.
The user model looks something like this:
module.exports = function(sequelize, DataTypes) {
return sequelize.define('user', {
id: {
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4
},
email: DataTypes.STRING,
password: DataTypes.STRING,
}, {
classMethods: {
generateHash: function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
},
},
instanceMethods: {
validPassword: function(password) {
return bcrypt.compareSync(password, this.password);
}
},
getterMethods: {
someValue: function() {
return this.someValue;
}
},
setterMethods: {
someValue: function(value) {
this.someValue = value;
}
}
});
}
Everything seems to work just fine. I can sign up using this strategy, log in, and see data.
I also am using Express and have various routes set. The req.user appears to be set correctly, as I can interact with all the fields of this object.
Consider the sample route which works correctly:
app.get('/profile', isLoggedIn, function(req, res) {
res.render('profile.ejs', {
user : req.user
});
});
My serialization / deserialization methods:
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id).then(function(user) {
done(null, user);
}).catch(function(e) {
done(e, false);
});
});
So, as per the Passport JS documentation, the user session seems to be correctly set and hooked into Express.
The trouble is that I cannot update any of the fields in the user object.
If I have the following route:
app.get('/change-email', function(req, res) {
req.user.email = req.body.email;
res.status(200).end();
});
Nothing happens in the database.
This is very similar to this question except it appears with Sequalize, the user object never persists, even after logging out and back in again.
I have also tried: req.session.passport.user.email = req.body.email
Although I didn't think this would fix the problem, I also tried to call login with the new user object, but this generated 500 errors. Which such a limited number of functions that can be called, according to the Passport JS documentation, I'm starting to question if this functionality is even possible.
I'm not sure what to try from here. How can I update my user object using Sequelize?
Any help would be greatly appreciated! Thanks!
// Edit: Rewriting first paragraph to be clearer
When you change any column values in an Instance object you also need to explicitly save the changes to the database to persist them. Since you are already storing the user Instance in req.user via passport.deserializeUser you only need to make a small change to your routing code to do this.
Instead of your current route for /change-email, I suggest:
app.get('/change-email', function(req, res) {
req.user.email = req.body.email;
req.user.save()
.then(function() {
res.status(200).end();
});
});
For more information on how to persist changes to an instance, see this part of the Sequelize documentation.
I know it's a late answer but no wonder someone will pass around this in the near future,
anyway, to get the authenticated user information in req.user with sequelize version 6.* and Express
here is the trick:
//a middleware to verify if a user is authenticated or not
exports.verifyToken = async (req, res, next) => {
let token = req.headers.bearer;
if (!token) {
return res.status(403).send({
message: "No token provided!"
});
}
jwt.verify(token, secretKey, async (err, decoded) => {
if (err) {
return res.status(401).send({
message: "Unauthorized!"
});
}
//$$$$$$$$$ here is everything you want to do $$$$$$$$$$$$$$$
req.user = await Users.findByPk(decoded.id, { raw: true });
next();
});
};
and here is an example where we can use that middleware
.get('/wishlist', auth.verifyToken, (req, res, next) => {
console.log(req.user);
})
the output will be something like this:
{
id: '9313e6e5-7b04-4520-8dbc-d04fad3a0cb1',
fullName: 'Anis Dhaoui',
avatar: 'images/imagelink.jpg',
username: 'anis',
}
and of course, you can modify the output of your findByPk or findOne query using include or exclude see Sequelize docs
https://sequelize.org/docs/v6/core-concepts/model-querying-basics/