How to protect the password field in Mongoose/MongoDB so it won't return in a query when I populate collections?
Suppose I have two collections/schemas. One is the Users Schema with username and password fields, then, I have a Blogs Schema that has a reference to the Users Schema in the author field. If I use Mongoose to do something like
Blogs.findOne({...}).populate("user").exec()
I will have the Blog document and the user populated too, but how do I prevent Mongoose/MongoDB from returning the password field? The password field is hashed but it shouldn't be returned.
I know I can omit the password field and return the rest of the fields in a simple query, but how do I do that with populate. Also, is there any elegant way to do this?
Also, in some situations I do need to get the password field, like when the user wants to login or change the password.
You can change the default behavior at the schema definition level using the select
attribute of the field:
password: { type: String, select: false }
Then you can pull it in as needed in find
and populate
calls via field selection as '+password'
. For example:
Users.findOne({_id: id}).select('+password').exec(...);
.populate('user' , '-password')
http://mongoosejs.com/docs/populate.html
JohnnyHKs answer using Schema options is probably the way to go here.
Also note that query.exclude()
only exists in the 2.x branch.
Edit:
After trying both approaches, I found that the exclude always approach wasn't working for me for some reason using passport-local strategy, don't really know why.
So, this is what I ended up using:
Blogs.findOne({_id: id})
.populate("user", "-password -someOtherField -AnotherField")
.populate("comments.items.user")
.exec(function(error, result) {
if(error) handleError(error);
callback(error, result);
});
There's nothing wrong with the exclude always approach, it just didn't work with passport for some reason, my tests told me that in fact the password was being excluded / included when I wanted. The only problem with the include always approach is that I basically need to go through every call I do to the database and exclude the password which is a lot of work.
After a couple of great answers I found out there are two ways of doing this, the "always include and exclude sometimes" and the "always exclude and include sometimes"?
An example of both:
The include always but exclude sometimes example:
Users.find().select("-password")
or
Users.find().exclude("password")
The exlucde always but include sometimes example:
Users.find().select("+password")
but you must define in the schema:
password: { type: String, select: false }
User.find().select('-password')
is the right answer. You can not add select: false
on the Schema since it will not work, if you want to login.
You can achieve that using the schema, for example:
const UserSchema = new Schema({/* */})
UserSchema.set('toJSON', {
transform: function(doc, ret, opt) {
delete ret['password']
return ret
}
})
const User = mongoose.model('User', UserSchema)
User.findOne() // This should return an object excluding the password field