I made my own login system in nodejs and I have couple question about that.
To check user login I make this:
function loadUser (req, res, next) {
// Check user_id
if (req.session.user_id) {
// Is there in db
User.findById({_id: req.session.user_id}, function (err, user) {
if (user) {
req.currentUser = user;
next();
} else {
res.redirect('/login');
}
});
}
}
app.get('/secure', loadUser, function (req, res) {
res.render('secure.jade', {user: req.currentUser});
});
how safe is it? Can a hacker to pick up a session key? And are there best practice to make this approach better
Well if the hacker steals the cookie of the user he can impersonate him, but that's the case for many websites. You shouldn't worry to much about that though.
Also, it's better to have the username remembered along with the user_id, no point in making two queries over the time.
Related
We are currently using the https://github.com/panva/node-openid-client strategy for passportJS, along with cookie-session.
However we would like to try and move away from sessions and just store either a simple cookie with a token, or attach the token to a header on each request which we then introspect to see whether the token is valid.
I can't figure it out, maybe it's not possible, I just simply don't know where or how I can retrieve the token from the openid-client library, and when and how I should save it in a cookie. Maybe it is only built to use a session.
currently we have:
passport.use(
`oidc.${site}`,
new Strategy(
{
client,
params: getParamsForSite(site),
passReqToCallback,
usePKCE,
},
(tokenset, done) => {
const user = {
token: tokenset,
name: tokenset.claims.sub,
};
return done(null, user);
}
)
);
for the login
app.get(['/login', '/login/:site'], (req, res, next) => {
if (req.params.site) {
passport.authenticate(`oidc.${req.params.site}`)(req, res, next);
} else {
res.end(loginFrontend.success());
}
});
and for the callback
app.get('/auth_callback', (req, res, next) => {
passport.authenticate(`oidc.${req.query.state}`, {
callback: true,
successReturnToOrRedirect: process.env.BASE_URI,
})(req, res, next);
});
We would like to continue using this library as the authentication service we call has a discovery endpoint etc. and wouldn't want to implement all of the features ourselves. If I set session to false, how do I retrieve the token and where for this strategy, can someone help me?
I have two user collections in my db and I want to make different login types for every one, so I have made two strategy on passport for my site ('local-user' & 'local-manager').
My question is how to check logged in user type (by used strategy) in my app?
In this code, passport just checks user auth but I want to check by strategy. (eg: if user logged in by 'local-manager', then show the page)
function isLoggedIn(req, res, next){
if (req.isAuthenticated()) {
next();
return;
}
res.redirect('/login');
}
It's better you use role mapping for this.
Anyway for now you can use this concept :
var passport = require('passport')
, LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
{passReqToCallback: true},
function(req, username, password, done) {
req.usedStrategy = 'local-user';
//do auth stuff
});
}
));
And use like this :
function isLoggedIn(req, res, next){
if (req.isAuthenticated() && req.usedStrategy === 'local-user') {
next();
return;
}
res.redirect('/login');
}
Also you can use session if you enable it in passport.
It must be said (and has been in other answers/comments) that you should really look again at your modelling of the domain. User objects can be really simple (just login information) and the rest can be broken out into other models/schemas.
Anyway on to the answer to your original question:
You can switch on the user type. Passport doesn't reach too far into the rest of your application. The log in strategies are not known about outside of the actual log in section.
You can handle that as middleware and add some extra information to the request object by checking for a unique property in one of the models:
function(request, response, next){
request.isManager = !!(request.user && request.user['unique_prop']);
next();
}
Place this after the auth middleware. Then in your route you can switch based on request.isManager. Also encapsulating this in middleware will abstract it from the user model and allow you to refactor it in the background.
Another alternative would be to add the function as a static/method/virtual (depending on the implementation) to the schema if you're using mongoose.
Hope this helps 👍 If you have further questions feel free to add comments and I can amend the answer. 🤔
I know this question is old, but I just had the same problem, and I managed to find a way to at least know what kind of account the user is. As someone else said, I don't think there is a way to see what strategy a user used to log in, but if you have a field in your user database to show what kind of account they are (e.g.: admin, client, etc.), then you can pass this information along in the req.user variable when you serialize the user:
passport.serializeUser(function(user, cb) {
process.nextTick(function() {
return cb(null, {
id: user.user_id,
username: user.user_email,
type: user.user_type
});
});
});
passport.deserializeUser(function(user, cb) {
process.nextTick(function() {
return cb(null, user);
});
});
In the example above, I'm using an SQL database, and one of the columns is called user_type. Once the user is deserialized, passport passes on the fields you ask for in passport.serialize() to req.user. So, if you wanted to know what type of account is currently logged in, you could say something like:
console.log("This user is a(n) " + req.user.type);
Or, something more realistic, if you're using Express:
app.get("/adminsOnly", (req, res) {
if (req.isAuthenticated() { // returns true if a user successfully logged in
if (req.user.type === "admin") { //check if user is an admin
res.render("adminsOnly.ejs"); //render admin section of website if true
} else {
res.redirect("/adminsLogin"); //redirected somewhere else because user is not an admin
}
} else {
res.redirect("/login"); //req.isAuthenticated() returns false, so user is redirected to the login page
}
});
I'm pretty new to coding in general, so I'm sure there are better ways to tackle this question, but this is a way to work around the problem of not being able to pinpoint which strategy the user logged in with.
I used meanjs to generate a project.
There are hasAuthorization functions generated automatically for crud modules. Using the articles example in app/controllers/articles.server.controller.js the authorization is something like:
exports.hasAuthorization = function(req, res, next) {
if (req.article.user.id !== req.user.id) {
return res.status(403).send('User is not authorized');
}
next();
};
I want to add in user roles so admin or owner can edit like:
exports.hasAuthorization = function(req, res, next) {
if (req.article.user.id !== req.user.id) {
var isAdmin = false;
for (var userRoleIndex in req.user.roles) {
if ('admin' === req.user.roles[userRoleIndex]) {
isAdmin = true;
}
}
if (!isAdmin) {
return res.status(403).send('User is not authorized');
}
}
next();
};
Question 1 is this secure? Or maybe better question is How secure is this?
Question 2 if it is not secure then what is the Meanjs way to make it secure?
Do I have to do something like in this SO Question or is something already built in?
You don't have to do anything like in that answer, because (if I remember fine) meanjs is using passportjs for that stuff.
If I may suggest you, you could pass role from route, so you will not have admin role hardcoded, and this method will be more reusable. You can than use it like this:
app.route('/articles/:id')
.get(users.requiresLogin, users.hasAuthorization(['user']), articles.read)
.delete(users.requiresLogin, users.hasAuthorization(['admin']), articles.delete);
I am working on a login interface using the MEAN stack. I have managed to get it to work using PassportJS. My problem now is I need a way to let my client-side know whether the person logging in is an admin or user(user role). These info are available from my MongoDB.
The flow of my API call is as follow :
app.post('/login', passport.authenticate('local'), authRoutes.loginCheck);
First, it runs the passport.authenticate where it calls the function below
function verifyCredentials(username, password, done) // username & password from what user provide when logging in
{
console.log('VC');
User.findOne({username: username}, function(err, user) //query Mongo
{
console.log(user); // User role is available here, in JSON format
if(user === null) // if no username in database, do this
{
console.log('Username does not exist in database');
}
else
{
user.comparePassword(password, function(err, match) // function written to compare hashed password in Mongo & password provided by user
{
if(match)
{
done(null, {id: username, name: username});
return user; // this is not the correct syntax, but the idea is, I want to send over the user details here, so I can access the role later
}
else
{
done(null, null);
}
});
}
});
}
The verifyFunction is called with this syntax.
passport.use(new LocalStrategy(verifyCredentials));
Once that function is successfully called, the server executes the 2nd part of it which is the loginCheck.
module.exports.loginCheck = function(req, res)
{
console.log('Calling loginCheck route');
// I generate some sort of jwt token here
// payload, body, blah blah blah ...
console.log(req.body);
res.json({
authenticated: req.isAuthenticated(), //built-in authentication function, returns true or false
token: token // sends over token
role: user.role // want to send over something like this
}); // sends all these to client side as JSON
}
Since both functions are in different files, I am unclear if I have to require something or simply just pass an extra parameter to the loginCheck function. I have tried the latter though and it did not work.
One way that I could think of is do another Mongo query in the loginCheck function, but that would be kinda redundant.
Even a specific keyword for me to google up would definitely be of big help as don't I know what I should be looking for. The reason is because I am new to NodeJS, thus I am not familiarize with most of the terms yet.
I think these codes should suffice but if I am needed to provide more, let me know and I will do so. Thanks in advance !!
To pass control to next matching route you need to use next that passes as third argument in the routes:
function verifyCredentials(req, res, next) {
User.findOne({username: req.body.username}, function(err, user) //query Mongo
{
if(user === null) {
return next(new Error('Username does not exist in database'));
} else {
user.comparePassword(req.body.password, function(err, match) {
if(match) {
next(null, {id: username, name: username});
} else {
next(new Error('not match'));
}
});
}
});
}
app.post('/login', verifyCredentials, authRoutes.loginCheck);
I am trying to build a RESTful API using Node.js w/ Express. I am fairly new to the MEAN stack, and want to use best practices. The concept I'm having trouble grasping and implementing is the following:
Restricting routes like PUT and DELETE on a user object, to only allow requests from users who 'own' this object.
One possibility I've thought of:
Creating secret token for users that matches token in DB
So when creating a user I assign them a token, store this in the DB and attach it to their session data.
Then my middleware would look something like:
router.put('/api/users/:user_id', function(req, res, next) {
// already unclear how this token should be transfered
var token = req.headers['x-access-token'] || req.session.token;
// update user (PUT /api/users/:user_id)
User.findById(req.params.user_id, function(err, user) {
if (err) {
res.send(err);
} else if (user.token != token) {
res.json({ sucess: false, message: 'User not same as authenticated user.' });
} else {
// set new information only if present in request
if (req.body.name) user.name = req.body.name;
if (req.body.username) user.username = req.body.username;
...
// save user
user.save(function(err) {
if (err) res.send(err);
// return message
res.json({ message: 'User updated.' });
});
}
});
Questions I have regarding best practice
Is the scenario I thought of at all plausible?
What data should I use to create a unique token for a user?
Is storing the token in the session the best solution?
Sidenote
This is a learning project for me, and I am aware of libraries like Passport.js. I want to learn the fundamentals first.
I have a repo for this project if you need to see some of the surrounding code I'm using: https://github.com/messerli90/node-api-ownership
Edit
I would accept a good RESTful API book recommendation, where these points are covered, as an answer.
Edit 2
I actually found a lot of the answers I was looking for in this tutorial: http://scottksmith.com/blog/2014/05/29/beer-locker-building-a-restful-api-with-node-passport/
I was trying to do this without the use of passport.js but a lot of the concepts covered in the article made some of the mechanics of an authorized API clear to me.
If I understand your question, this is an API, and the client (not a browser) is passing the secret token (api key) in the request, in a header. Seems reasonable. Of course, you must require https to protect the api key. And, you should have a way for users to revoke/regenerate their API key.
So far, I don't think you need to store anything in the session. It seems like storing the token in the session just complicates things. Presumably, if you are going to establish a session, the client has to include the token in the first request. So, why not just require it on each request and forget the session? I think this makes life simpler for the api client.
A 'bit' too late, but if someone is still looking for an answer, here is how i did it:
router.put('/', function(req, res) {
var token = req.headers['x-access-token'];
if (!token) return res.status(401).send({auth:false, message:'No token provided'});
jwt.verify (token, process.env.SECRET, function (err, decoded) {
if(err) return res.status(500).send({auth:false, message:'failed to auth token'});
User.findByIdAndUpdate({_id: decoded.user_id}, req.body, function(err, user) {
if (err)
res.send(err);
res.json({username: user.username, email: user.email});
});
});
});
Just pass the user id that is stored in the token to the mongoose function. This way the user who sent the request can only update or delete the model with his ID.
Reading material:
Implementing Access Control in Node.JS
Found this super clear article on how to allow users to only delete replies they own. Hope it helps.
What worked for me:
.delete(requireAuth, async (req, res, next) => {
const knexInstance = req.app.get("db");
const comment = await CommentsService.getById(knexInstance, req.params.id);
if (comment === undefined) {
return res.status(404).json({
error: {
message: `Comment doesn't exist.`
},
});
}
if (comment.users_id !== req.users.id) {
return res.status(401).json({
error: {
message: `You can only delete your own comments.`
},
});
}
CommentsService.deleteComment(knexInstance, req.params.id)
.then((numRowsAffected) => {
res.status(204).end();
})
.catch(next);
})