ExpressJS authorizing users with middleware? - node.js

Let's say I want to restrict the access of a resource to a certain group of people who meet some conditions. What I'm doing right now is defining an authorization middleware that checks if the req.user is meeting those conditions.
module.exports.requiresCondition = function(req, res, next){
Model.findOne({condition: condition}, function(err, model){
//check if various conditions are met. If not, return 401
res.locals.model = model;
return next();
}
}
The problem I have with this is that I can't choose what data to project because the routes that come after might use different parts of the model. This means I have to get the whole model every time, which becomes inefficient as the documents get larger and larger. Of course, I can just query once in the middleware with the keys I need to authorize and query again in the actual controller, but that doesn't seem particularly efficient either. Is there a better way to authorize users?

If different routes require different data you can modularize your middleware layer. composable-middelware https://www.npmjs.com/package/composable-middleware is perfect for that.
So let's say you have couple of routes:
import compose from 'composable-middleware';
router.get('/type1/:id', compose().use(Auth.hasAppAccess()).use(TypeMiddleware.getType1()), ctrl.doType1);
router.get('/type2/:id', compose().use(Auth.hasAppAccess()).use(TypeMiddleware.getType2()).use(TypeMiddleware.evenMoreOperations()), ctrl.doType2);
Then your middleware itself
import compose from 'composable-middleware';
export function isAuthenticated() {
return compose()
.use(function(req, res, next) {
//do your stuff here
})
}
//similarly for other middelware functions

Related

Node.js resource based ACL

I am implementing a simple Access Control system in Node, and I am wondering what can be the best approach for what I am doing.
I am using Node ACL and it is not clear to me how to block on a per-resource basis.
Let's take the following example:
USER ->* PROJECT ->* ENTRY. Users can have multiple projects which contains many entries. Users can be ADMIN or USER.
I created an endpoint /entry/{ID} where user can access an entry detail. The endpoint is accessible to everyone, ADMINs can see all entries, but for User I need to do something similar:
app.get('/entry/{id}', (req, res) => {
if (user.admin) {
// Return eveything
}
else {
if (entry.project == user.project) {
// return it
}
else {
// Unathorized
}
}
})
Is there a better approach/pattern to implement this checks on ownership on a resource?
It's a very broad question so I'll try to give you a couple hints as my answer, but
Is there an ACL pattern in javascript?
There's a number of solutions but I wouldn't call any of those a pattern. I'll be very subjective now, but the ways of passport.js and similar modules are non-transparent to say the least - and it's not really an ACL...
Someone may say - hey, it's node.js, there must be module to do that and make your node_modules heavier but searching for a good acl module in npm, I only found some outdated ones and tightly bound with express. Since your question wasn't which is the best npm module for acl I gave up looking for such at page 3, which doesn't mean there ain't something ready so you may want to look more closely.
I think your implementation could be considered acceptable, with some minor corrections or hints as I mentioned:
Separate your request logic from access control logic
In your code everything happens in one callback - that's definitely very efficient, but also very hard to support in longer term. You see, it'll end up in the same code in lots of those if's above in all the callbacks. It's very simple to separate the logic - simply implement the same path in two callbacks (they'll be run in the order they were defined), so:
app.all('/entry/{id}', (req, res, next) => {
const {user, entry} = extractFromRequest(req);
if (user.admin || entry.project === user.project) {
next();
} else {
res.status(403).send("Forbidden");
}
});
app.get('/entry/{id}', (req, res) => {
// simply respond here
})
This way the first callback checks if the user has access and this won't affect the logic of the response. The usage of the next() is specific to express-like frameworks which I assumed you use looking at your code - when you call it the next handler will be executed, otherwise no other handlers will be run.
See Express.js app.all documentation for an acl example.
Use a service wide acl
It's much more secure to keep a basic ACL in a single place and not to define it per path unless necessary. This way you won't omit one path and won't leave a security hole somewhere in middle of request. For this we need to split the ACL into parts:
URL access check (if path is public/open for all users)
User and session validity check (user is logged in, session is not expired)
Admin/user check (so permission level)
Otherwise we don't allow anything.
app.all('*', (req, res, next) => {
if (path.isPublic) next(); // public paths can be unlogged
else if (user.valid && user.expires > Date.now()) next(); // session and user must be valid
else if (user.admin) next(); // admin can go anywhere
else if (path.isOpen && user.valid) next(); // paths for logged in users may also pass
else throw new Error("Forbidden");
});
This check is not very restrictive but we won't need to repeat ourselves. Also notice the throw Error at the bottom - we'll handle this in an error handler:
app.use(function (err, req, res, next) {
if (err.message === "Forbidden") res.status(403).send("Forbidden");
else res.status(500).send("Something broke");
})
Any handler with 4 arguments will be considered an error handler by Express.js.
On the specific path level if there's any need for ACL's, simply throw an error to the handler:
app.all('/entry/{id}', (req, res, next) => {
if (!user.admin && user.project !== entry.project) throw new Error("Forbidden");
// then respond...
});
Which reminds me of another hint...
Don't use user.admin
Ok, fine, use it if you like. I don't. The first attempt to hack your code will be by trying to set admin on any object that has properties. It's a common name in a common security check so it's like leaving your WiFI AP login at factory defaults.
I'd recommend using roles and permissions. A role contains a set of permissions, a user has some roles (or one role which is simpler but gives you less options). Roles may be also assigned to project.
It's easily a whole article about this so here's some further reading on Role-based ACL.
Use standard HTTP responses
Some of this mentioned above, but it's a good practice to simply use one of standard 4xx HTTP code status as response - this will be meaningful for the client. In essence reply 401 when the user is not logged in (or session expired), 403 when there's no sufficient priviledge, 429 when use limits are exceeded. more codes and what to do when the request is a teapot in Wikipedia.
As to implementation itself I do like to create a simple AuthError class and use it to throw errors from the app.
class AuthError extends Error {
constructor(status, message = "Access denied") {
super(message);
this.status = status;
}
}
It's really easy to both handle and throw such an error in the code, like this:
app.all('*', (req, res, next) => {
// check if all good, but be more talkative otherwise
if (!path.isOpen && !user.valid) throw new AuthError(401, "Unauthenticated");
throw new AuthError(403);
});
function checkRoles(user, entry) {
// do some checks or...
throw new AuthError(403, "Insufficient Priviledges");
}
app.get('/entry/{id}', (req, res) => {
checkRoles(user, entry); // throws AuthError
// or respond...
})
And in your error handler you send your status/message as caught from your code:
app.use(function (err, req, res, next) {
if (err instanceof AuthError) res.send(err.status).send(err.message);
else res.status(500).send('Something broke!')
})
Don't reply immediately
Finally - this is more of a security feature and a safety feature at the same time. Every time you respond with an error message, why not sleep a couple seconds? This will hurt you in terms of memory, but it will hurt just a little and it'll hurt a possible attacker a lot because they wait for the outcome longer. Moreover it's super simple to implement in just one place:
app.use(function (err, req, res, next) {
// some errors from the app can be handled here - you can respond immediately if
// you think it's better.
if (err instanceof AppError) return res.send(err.status).send(err.message);
setTimeout(() => {
if (err instanceof AuthError) res.send(err.status).send(err.message);
else res.status(500).send('Something broke!')
}, 3000);
})
Phew... I don't think this list is exhaustive, but in my view it's a sensible start.

passing multiple collections to the same express route

app.get('/dbpage', function(req, res){
Chapters.find({}, function(err, allChapters){
if(err){
console.log(err);
}else{
res.render('dbpage', {chapters: allChapters});
}
});
});
The code above is a small portion of my project, and it's working fine. Chapters is the collection's name which is part of my books database. But I have another collection (from the same database) that I'm trying to pass through the same route /dbpage. How would add this other collection to the code above? So I can access the information stored in this other collection in the /dbpage route.
I've seen some other answers that are more or less suited for what I'm trying to do, but they all seen overkill for such a simple thing. Any suggestions? Thanks!!
You can't invoke the same route name. If you use duplicate route definitions, the one "listed first" will take precedence. The only time the same route definition is allowed is if you utilize a different HTTP verb... such as POST, PUT, PATCH, etc.
Thus, if you truly want to use the same route, you need to pass query params and then push conditional logic in the route, such as:
app.get('/dbpage', function(req, res){
let {someQueryParam} = req.query;
if(someQueryParam === 'someSpecialValue'){
//... do something different...
} else {
Chapters.find({}, function(err, allChapters){
if(err){
console.log(err);
}else{
res.render('dbpage', {chapters: allChapters});
}
}
});
});
And, you'd invoke it with some endpoint such as:
yourDomain.com:3000/dbPage?someQueryParam="someSpecialValue"
Honestly though I'd advise against introducing conditional logic when at all possible. If possible, just set up another endpoint.

nodeJS code pattern => Express middleware/Oauth2/Passport

I inherited a codebase where it looks like they run middleware in node with the following pattern for Oauth2 passport strategy
module.exports = function (router) {
router.get('/', function (req, res, next) {
passport.authenticate('type', object, function(err, info) {
//pass info object to next middleware
})(req,res,next) <---where does this go?!?
})
}
From my current understanding of the code base, this is actually the last function call in the middleware chain, so could I just add a piece of middleware to the bottom?
Does this sound like the right idea?
And just to clarify what I'm trying to do:
pass data from Oauth callback through middleware function by attaching it to the req
perform DB business logic (create or lookup account)
login with JWT
redirect
This appears to be the "custom callback" method of using passport's authenticate function. If you look at the documentation you can see how they expect it to be used. That said, I don't know what that second argument is supposed to be doing (the object) - it looks like a variable, but I don't see it defined anywhere, and I'm not sure the authenticate method takes arguments in that manner. Also, the custom callback takes three arguments: err, user, and then info... which might trip you up.
Okay, so now to your actual question of "could I just add a piece of middleware to the bottom?" Sort of? The fact is, you're in a routing middleware at that point. If it matches and auth is successful, then you should do whatever code for that route is required inside the custom callback. That's the point of this way of doing things. Alternatively you could use passport.authenticate as a piece of middleware itself (it returns a middleware function usable in the CommonJS pattern.
If you don't want to change up the code, then you could just do this:
module.exports = function (router) {
router.get('/', function (req, res, next) {
passport.authenticate('PICK A VALID TYPE', function(err, user, info) {
// this custom callback will be executed once auth completes
// (either successfully or not
// put code in here to perform DB business logic, login, and redirect
})(req,res,next); <--- this executes the passport.authenticate middleware
})
};

express js - How is one http request different from other?

I've been working on creating a better architecture for rest api in express and node. Let's say I have 3 methods in my route middleware -
router.post('/users/:id', [
UserService.getUserById,
UserController.setUser,
MailerService.sendSubscriptionMail
]);
I am setting up req.session.user in call to UserService.getUserById and then using that to set req.session.result in UserController.setUser. Now I am sending a mail to this user using the data stored in req.session.result.
UserService -
exports.getUserById = function(req, res, next) {
.
.
req.session.user = data;
.
.
};
module.exports = exports;
UserController -
exports.setUser = function(req, res, next) {
.
.
req.session.result = req.session.user;
.
.
};
module.exports = exports;
MailerService -
exports.sendSubscriptionMail = function(req, res, next) {
// Using req.session.result here to send email
};
module.exports = exports;
Now I have two questions regarding above process -
(a) Is there any chance that a new http req to another route (which also has these kind of methods which can modify req.session) can modify the req.session.result and MailerService.sendSubscriptionMail does not get the data which it needs to send to the user or will that req object will be completely different from this one in the memory?
(b) Is there any other method to transfer data between middleware rather than setting up req object?
Is there any chance that a new http req to another route (which also
has these kind of methods which can modify req.session) can modify the
req.session.result and MailerService.sendSubscriptionMail does not get
the data which it needs to send to the user or will that req object
will be completely different from this one in the memory?
The req object is specific to this request. That object cannot be changed by another request. But, if the session in req.session is a common shared sesison object that all requests from that particular user share, then req.session.result could be changed by another request from that user that is getting processed at around the same time (e.g. interleaved within various async operations).
If you want to make sure that no other request from this user could change your result, then put it in req.result, not req.session.result because no other requests will have access to req.result.
Is there any other method to transfer data between middleware rather
than setting up req object?
The req or res objects are the right places to share info among middleware handlers for the same request as they are unique to this particular request. Be careful with the session object because it is shared among multiple requests from the same user.
Another possible way to share the data among your three handlers is to make a single middleware handler that calls all three of your middleware handlers and then share the data within that single function (e.g. passing it to the other handlers).
For example, you could change the calling signature of your 2nd two methods so you can get the data out of setUser() and then pass it directly to sendSubscriptionMail() without using the req or session objects to store it.
router.post('/users/:id', [UserService.getUserById, function(req, res, next) {
UserController.setUser(req, res, function(err, result) {
if (err) return next(err);
MailerService.sendSubscriptionMail(result, req, res, next);
}]);
});

multiple routes with differents params, calling the same ressource

Is it possible with expressjs to have multiple routes calling the same resource, something like that:
app.get('/users/:user_id', users.getOne)
app.get('/users/:username', users.getOne)
I would like to be able to call users.getOne whichever params (:user_id or :username) is used in the get request.
In the users.getOne function, how can I determine wich one was used and build my query according to it?
exports.getOne = function(req, res){
var queryParams = ? // I need help here
Users
.find(queryParams)
...
Thanks!
Possibly related: express.js - single routing handler for multiple routes in a single line
From express's view, both of those routes will match the same set of request URLs. You only need one of them and you can name it to make more sense:
app.get('/users/:key', users.getOne);
//...
// http://stackoverflow.com/a/20988824/266795
var OBJECT_ID_RE = /^[a-f\d]{24}$/i;
exports.getOne = function(req, res) {
var conditions = {_id: req.params.key};
if (!OBJECT_ID_RE.test(req.params.key)) {
conditions = {username: req.params.key};
}
Users.find(conditions)...
If you end up wanting this pattern in many routes throughout your code base, you can extract it into a /users/:user param and use app.param as per #alex's answer, but encapsulate the code to locate the user and stick it on to req.user so the actual route handler can just assume the user has been properly found and loaded by the time it executes, and 404 handling can be centralized as well.
Those are in fact, from express's view, the same route.
No, they are not. One route has :user_id parameter, another one has :username.
This would be a proper solution:
var OBJECT_ID_RE = /^[a-f\d]{24}$/i;
app.param('user_id', function(req, res, next, value, name) {
if (OBJECT_ID_RE.test(value)) {
next()
} else {
next('route')
}
})
app.get('/users/:user_id', users.getOne)
app.get('/users/:username', users.getOne)
app.param set the prerequisite for the route to be called. This way when user_id matches a pattern, first route gets called, otherwise second one.

Resources