Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Passport authentication for Sails.js 0.9.x
/**
* api/services/auth-basic.js
*
* Basic authentication strategy is defined here.
* Other strategies can be defined as needed by adding files like this to the services folder.
*
**/
var passport = require('passport'),
BasicStrategy = require('passport-http').BasicStrategy;
passport.use(new BasicStrategy(function(username, password, next) {
User.findOneByUsername(username).done(function(err, user) {
if (err) {
return next(err);
}
if (!user) {
return next(null, false);
}
user.validPassword(password, function(err, res) {
if (err) {
return next(err);
}
next (null, res ? user : false);
});
});
}));
/**
* api/policies/authenticated.js
*
* This example shows how to use the HTTP Basic authentication strategy using the passport-http module.
* Other strategies (Digest, OAuth, OAuth2, etc) can be similarly implemented.
*
**/
var express = require('express'),
app = express(),
passport = require('passport'),
local = require('../../config/local');
app.use(passport.initialize());
/**
* Allow any authenticated user.
*/
module.exports = function(req, res, ok) {
// User is allowed, proceed to controller
passport.authenticate(local.auth, {session: false}, function(err, user, info) {
if (err || !user) {
return res.send("You are not permitted to perform this action.", 403);
}
return ok();
})(req, res, ok);
};
/**
* config/local.js
*
**/
module.exports = {
port: 1337,
environment: 'development',
adapter: {
mongo: {
module: 'sails-mongo',
host: 'localhost',
// user : 'username',
// password : 'password',
database: 'jscart_dev'
}
},
auth: 'basic' //This is where the authentication strategy to be used is defined.
};
/**
* config/policies.js
*
**/
module.exports.policies = {
// Default policy for all controllers and actions
// (`true` allows public access)
'*': 'authenticated' //define the policy to be used globally, or specific to controllers/actions.
}
/**
* api/models/User.js
*
* The user model contains the instance method for validating the password.
*/
var bcrypt = require('bcrypt');
function hashPassword(values, next) {
bcrypt.hash(values.password, 10, function(err, hash) {
if (err) {
return next(err);
}
values.password = hash;
next();
});
}
module.exports = {
attributes: {
username: {
type: 'STRING',
required: true,
unique: true
},
password: {
type: 'STRING',
required: true,
minLength: 6
},
email: {
type: 'email',
required: true,
unique: true
},
// Override toJSON instance method to remove password value
toJSON: function() {
var obj = this.toObject();
delete obj.password;
return obj;
},
validPassword: function(password, callback) {
var obj = this.toObject();
if (callback) {
//callback (err, res)
return bcrypt.compare(password, obj.password, callback);
}
return bcrypt.compareSync(password, obj.password);
}
},
// Lifecycle Callbacks
beforeCreate: function(values, next) {
hashPassword(values, next);
},
beforeUpdate: function(values, next) {
if (values.password) {
hashPassword(values, next);
}
else {
//IMPORTANT: The following is only needed when a BLANK password param gets submitted through a form. Otherwise, a next() call is enough.
User.findOne(values.id).done(function(err, user) {
if (err) {
next(err);
}
else {
values.password = user.password;
next();
}
});
}
}
};

where do (to which route? how to configure?) i need to send my post with username and password to auth me now?

Owner

adityamukho commented Aug 25, 2013

The authentication policy will be applied to each route for which the 'authenticated' policy has been assigned. In this example, the last file (policies.js) enforces this policy on all routes. You will need to fine tune this policies file in order to specifically define which routes the policy should apply to.

Note that this is an example of HTTP Basic authentication and each request to an authenticated route will need to supply the authentication credentials.

Excellent example. This is extremely helpful, thank you so much!

Can you please explain in your model lifecycle callbacks, what you're doing in beforeUpdate()?

I understand that if the user is changing their password, then you want to hash it before writing to the database (lines 54 and 55), but if the user is not changing their password, why do you make a database query and set the password to the existing password?

Is there any particular reason you used bcrypt's compareSync() method rather than the async version?

Owner

adityamukho commented Aug 28, 2013

@winduptoy

  1. I'm re-inserting the password field into the user object before saving, in the case where the user leaves the password unchanged, for the following reason:
    1. The user receives the user object from server, which they intend to modify (through a form or some other means). This user object has the password field already cleared out (see the toJson() instance method).
    2. If the user does not input a new password, then the modified object is submitted with a blank password field.
    3. Since the password field is marked 'required' in the model, waterline would refuse to save it as is. Even if the password field were (for some strange reason) marked as optional, we would still have to re-insert the original password, or we would lose it on saving the object.
  2. I've updated the gist to use the asynchronous compare() method by default.

Hope that helps.

EDIT: Sails is smart enough to update provided fields in place without touching others, for an existing model instance. So if the password param is not sent at all in the request body, it would be left intact. The re-insertion is required only in the case where a blank password param comes through from the form submit.

...? And how authenticate?

Owner

adityamukho commented Jan 21, 2014

authentication is done by sending HTTP basic authentication headers with each request. See http://en.wikipedia.org/wiki/Basic_access_authentication for more details.

terox commented Jan 28, 2014

And if you need use the current user in other policy or in your app... how you can retrieve or use?

Owner

adityamukho commented Jan 29, 2014

HTTP Basic authentication does not use sessions, so if the current user needs to be retrieved in another part of the application, it can be done by storing the user object in a req attribute (in authenticated.js). This example does not demonstrate that use case, but it should be easy to implement, by adding the code to the exported middleware.

terox commented Jan 29, 2014

Thank you very much, I think also that req.yourattribute is the best place :)

With sails 0.9.x do not work. helpme please.

Owner

adityamukho commented Mar 11, 2014

My first suggestion would be to check if you're still actually on sails 0.9.x. If you have updated recently, you could be running 0.10.x, which has introduced several changes that will break this gist.

Once you've verified you're on the correct version, I'll need to see the errors/stacktraces in order to figure out what went wrong.

Owner

adityamukho commented Mar 11, 2014

Rant: I really wish github would enable notifications for comments posted to gists. It's really very easy to miss a new comment if one does not regularly check back on their old gists.

http://stackoverflow.com/questions/28393254/password-gets-changed-when-updating-user-model-in-sails-js

Can someone help me out? I am using the above code to update User Model. While updating password, it works fine but when i update another field of the user model, the passport again gets encrypted and it is changed while updating. (beforeUpdate)

@winduptoy @adityamukho

Solution for my above Query:

Now, only if you send password: password in the Update method, it will update the password (hash and store) else it will only update the provided user fields.

Controller(UserController.js):

updateDisplayName: function(req, res) {

var userid = req.token;
var newDisplayName = req.param('newdisplayname');

User.update({id: userid},{displayname: newDisplayName}).exec(function afterwards(err,updated){

if (err) {
res.json(err);
} else {
res.json("Success");
}
});

},

Model(User.js):

beforeUpdate: function(values, next) {
if(values.password) {
hashPassword(values, next);
} else {
next();
}
},
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment