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