I would like to use passport.js to verify that when users hit certain endpoints that they not only have the correct password but are a member of a specific group or have a certain access.
For simplicity sake if I have access levels of USER and ADMIN.
I can use passport to authenticate a password:
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username }, function(err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
if (!user.validPassword(password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}
));
Then with a route I can make sure the user passes auth:
app.get('/api/users',
passport.authenticate('local'),
function(req, res) {
res.json({ ... });
});
But lets say you need to have ADMIN acess to hit /api/users'. Do I need to write my own Strategies? IE do I need to have a local-user, and local-admin strategy, and in each verify the proper access levels?
I think I can do this pretty easily, but the problem arises when I need my site to have different auth methods (maybe sometimes use oauth), I would need to write custom *-user, *-admin strategies for each. Seems like overkill.
Other option is to just verify access/group in each route after the user has been authenticated. But I would prefer to do this in the middle-ware if possible.
Thanks
You could create a simple middleware that checks the group:
var needsGroup = function(group) {
return function(req, res, next) {
if (req.user && req.user.group === group)
next();
else
res.send(401, 'Unauthorized');
};
};
app.get('/api/users',
passport.authenticate('local'),
needsGroup('admin'),
function(req, res) {
...
});
This assumes that the object stored in req.user has a property group. This object is the one passed along from the strategy implementation and deserializeUser.
An alternative could be connect-roles, but I don't know how well that integrates with Passport.
EDIT: you could also combine Passport and the group-checking middleware:
var needsGroup = function(group) {
return [
passport.authenticate('local'),
function(req, res, next) {
if (req.user && req.user.group === group)
next();
else
res.send(401, 'Unauthorized');
}
];
};
app.get('/api/users', needsGroup('admin'), function(req, res) {
});
Related
Working on Passport.js authentication and want to display messages to client when login is not successful.
My current logic:
app.route('/login').post(passport.authenticate('local', { failureRedirect: '/' }),
(req, res) => {
res.redirect('/profile')
});
Also using similar thing in registeration which is working as I intended using connect-flash to display my message.
app.route('/register')
.post((req, res, next) => {
myDataBase.findOne({ username: req.body.username }, function (err, user) {
if (err) {
next(err);
} else if (user) {
req.flash('message', 'Username is already registered, please try different username');
res.redirect('/');
} else {........
How can I apply similar solution to my login logic? I suppose I need to create a custom callback function but could not figure it out.
I have an Express app with the passport-local strategy, using Mongoose for storing user Accounts. I had a freelancer write this part of the app for me because I couldn't make sense of how to build the login system from beginning to end (every single tutorial I found did it in a different way). The drawback of this is that I don't understand what each part does. This is in my app.js file:
const Account = require('./models/db/AccountsSchema');
app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy(Account.authenticate()));
passport.serializeUser(Account.serializeUser());
passport.deserializeUser(Account.deserializeUser());
and this is in routes/index.js:
router.post('/register', function(req, res) {
Account.register(new Account({
username: req.body.username,
name: req.body.name
}), req.body.password, function(err, account) {
if (err) {
console.log(err);
} else {
console.log(account);
passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/login'
})(req, res, function(err, user) {
res.redirect('/');
});
}
});
});
along with:
router.post('/login',
passport.authenticate('local'),
function(req, res) {
res.redirect('/');
}
);
Now in the login POST request I want to have a check for whether a user with that particular username exists, so that if the password is wrong at least I can tell the user that the password is wrong. User enumeration is not a security concern here.
Where in my code can I incorporate a database check for whether an Account with the specified username exists?
In Account.authenticate() function. As it sets the LocalStrategy in your case.
setting your local strategy:
passport.use(new LocalStrategy(Account.authenticate()));
Sample Code:
function authenticate(username, password, done) {
User.findOne({ username: username }, function(err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
if (!user.validPassword(password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}
I am trying to send the info message that gets set in my verify callback:
Here is the example from passport docs:
var passport = require('passport')
, LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username }, function(err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
if (!user.validPassword(password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}
));
However if I write my route as:
app.post('/login',
passport.authenticate('local'),
function(req, res) {
// If this function gets called, authentication was successful.
// `req.user` contains the authenticated user.
res.redirect('/users/' + req.user.username);
});
That function does not get called with the info message. At least not that I know of. I know passport shoves the user into the req, as I can access it from req.user. Is there a way to access the info message like this. Or do I need to specify a custom callback?
Which they outline as:
app.get('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err); }
if (!user) { return res.redirect('/login'); }
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.redirect('/users/' + user.username);
});
})(req, res, next);
});
The part that is confusing is that they outline using a 3rd parameter in the done callback (from verify) that is a message, yet you can only access that if you write a custom callback. Is that true.
The adopted convention in the Passport framework is that the optional info object will be available through req.authInfo.
This however depends on the used strategy which is responsible for passing it to the Passport framework. For instance, the passport-http strategy does not forward the info object to Passport while the passport-local does.
Here is how you can then access it in a controller protected by the local strategy :
app.post('/login',
passport.authenticate('local'),
function (req, res) {
if (req.authInfo) {
// req.authInfo.message will contain your actual message.
}
});
I'm surprised that it's so hard to find, or maybe something wrong with me.
I need a passport.js strategy that requires no fields - to run authentication with it on simple user GET request to '/' and manipulate user session data to, for example, make a new 'guest' user.
Maybe i can do this via passport local-strategy? Or am i need to create a custom one?
I saw this Configure Passport to accept request without body? question, yet i just simply can't figure out how the suggested in answer wrapping will change anything.
Edit: here's what I'm trying to achieve
passport.use('guest', new LocalStrategy({
passReqToCallback: true
},
function (req, NOusername, NOpassword, done) {
////NO PASSWORD OR USERNAME checks here, just deciding what kind of temporary user needs to be created///
if (req.session.passport) {
///create new user with previous session data
done(null,user)
}
else {
///create clean user
done(null, user)
})
)
and then in routes.js
app.get('/', passport.authenticate('guest'), function (req, res) {
res.render('PeerRoom.ejs', req.user);
});
Edit: Another approach could be using req.logIn and skip dealing with the strategies altogether
app.get('/', yourCustomGuestAuthenticationMiddleware, function (req, res) {
res.render('PeerRoom.ejs', req.user);
});
function yourCustomGuestAuthenticationMiddleware(req, res, next){
// No need to do anything if a user already exists.
if(req.user) return next();
// Create a new user and login with that
var user = new User({name: 'guest'+Math.random().toString() });
user.save();
req.logIn(user, next);
}
To make a new guest user, simply do it instead of rejecting an authentication
Here's a modified example from the docs
var passport = require('passport'),
LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username }, function(err, user) {
if (err) return done(err);
if (!user) {
/* HERE, INSTEAD OF REJECTING THE AUTHENTICATION */
// return done(null, false, { message: 'Incorrect username.' });
/* SIMPLY CREATE A NEW USER */
var user = new User({ name: 'guest'+Math.random().toString() });
user.save();
/* AND AUTHENTICATE WITH THAT */
return done(null, user);
}
if (!user.validPassword(password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}
));
I'm making a user account system for my new website using node.sdk,stormpath,express.js and passport.js . So I've set up an account with a custom data slot. I would like to know how can I post new data to this custom data slot when they log out and retrieve it when they log in.I'm new to using node and I don't know where to put my code or how to access the 'user' account info when they have logged in. From what I can tell passport.js is handling authentication so I probably can't see the users email to search for their user account url on the stormpath api... maybe I'm missing something here??
router.post('/register', function(req, res) {
var username = req.body.username; var password = req.body.password;
// Grab user fields. if (!username || !password) { return res.render('register', { title: 'Register', error: 'Email and password required.' }); }
// Initialize our Stormpath client. var apiKey = new stormpath.ApiKey( process.env['STORMPATH_API_KEY_ID'], process.env['STORMPATH_API_KEY_SECRET'] ); var spClient = new stormpath.Client({ apiKey: apiKey });
var app = spClient.getApplication(process.env['STORMPATH_APP_HREF'], function(err, app) { if (err) throw err;
account = {
givenName: 'John',
surname: 'Smith',
username: username,
email: username,
password: password,
customData:{
favList:'',
},
};
app.createAccount(account, function (err, createdAccount) {
if (err) {
return res.render('register', {'title': 'Register', error: err.userMessage });
} else {
passport.authenticate('stormpath')(req, res, function () {
return res.redirect('/home');
});
}
});
});
});
// Render the login page. router.get('/login', function(req, res) {
res.render('login', { title: 'Login', error: req.flash('error')[0] }); });
// Authenticate a user. router.post( '/login',
passport.authenticate( 'stormpath', { successRedirect: '/home', failureRedirect: '/login', failureFlash: 'Oops I guess you need an account to get in here..Soz', } ) );
// Render the dashboard page. router.get('/home', function (req, res) { if (!req.user || req.user.status !== 'ENABLED') { return res.redirect('/login'); }
res.render('home', { title: 'Home', user: req.user, } ); });
This is a great question. Thankfully the Passport API has you covered. You want to use a "Custom Callback" function, then you can get access to the user inside of that function. In the case of the Stormpath strategy the user object will be a Stormpath Account instance. However you will need to re-implement some of the redirection logic that you're currently passing in as options. Here is an example of how that would look with the Stormpath strategy:
app.post('/login', function(req, res, next) {
passport.authenticate('stormpath', function(err, user, info) {
if (err) {
return next(err);
}
else if (user) {
console.log('The account is: ', user);
req.logIn(user, function(err) {
if (err) {
next(err);
}else{
res.redirect('/dashboard');
}
});
}else{
req.flash('error',info.message);
res.redirect('/login');
}
})(req, res, next);
});
The docs for this custom strategy can be found here: http://passportjs.org/guide/authenticate/
Another note: I'd suggest creating your spClient outside of the route handler. The Stormpath Client can be used for multiple requests and only needs to be created once per process.