I have a Node, Express app with EJS at the front end.
I have this middleware function with passport that runs before all create, edit, delete routes.
function isLoggedIn(req, res, next) {
if (req.isAuthenticated()) {
if (req.user._id != "12345") {
res.redirect("/error");
}
return next();
}
res.redirect("/error");
}
The best I could come up with to verify that my admin user is the one trying to access the route is to check by user id in mongo db with req.user._id
Is there a better way to handle admin user access to routes and html components?
This looks essentially correct to me. It is important to note there are two layers: authentication and authorization.
Authentication is effectively a boolean: is the user authenticated? You have your function there req.isAuthenticated(). This might logically return a boolean, true or false, for whether or not the user is authenticated (ie: logged in).
Authorization could perhaps take many forms, but is effectively again a boolean: does this user meet the criteria to access this resource.
Authentication is usually well-served in a middleware, somewhere central that runs before "the endpoint", but authorization is not as simple because any endpoint could allow an operation or deny it, or it could respond differently depending on the user's privilege.
This whole conversation is perhaps quite deep in a roles & permissions discussion.
I think the answer is dependant on the app. Your app has two requirements: one, the user must be authenticated, and two, the user might need to be an admin.
The answer will be somewhere around: what is the most simple way to accomplish this?
In my opinion, you would consider the SOLID principles and note that you have one middleware, so it should have one responsibility: to check if the user is authenticated. Next, maybe you should have another middleware called isAdmin that runs for every endpoint that requires this extra condition. That's really all it is--an extra check. You shouldn't pollute your isLoggedIn middleware with that extra stuff because it makes that middleware less reuseable and less composable.
An isAdmin middleware would be a good idea, but it could also be a good idea to simply have it as a function inside every endpoint that requires that admin-check. Which way is better? Well first, which way is simpler. Which is less code but that is also still simple to understand.
Because this is roles and permissions, is there maybe a more robust way to keep track of which users are admins? If you have code that runs like if (req.user._id === 12345) {}, it requires special knowledge to remember this place in the code, so it is kind of brittle and "more likely" to fail. Maybe it would be a good idea to add a column to your users table for is_admin which could be null or 0 for every user except your user which could have 1. Then you could check if (req.user.is_admin) {}.
That might lead us to a middleware function like:
function isAdmin(req, res, next) {
if (req.isAuthenticated() && (req.user.is_admin === 1)) {
return next();
}
return res.redirect(403, "/error");
}
You could also do something like change that is_admin database column to instead something like role which could be 1 for every user except your admin users which could have maybe 2. That would allow you to do something like:
function hasAuthorization(req, res, next) {
if (req.isAuthenticated() && (req.user.role >= 2)) {
return next();
}
return res.redirect(403, "/error");
}
Such logic there can allow you to have increasing privilege roles: maybe 1 is regular, 2 is manager, 3 is admin, 4 is super-admin. If the user's role is less than 4, they don't have permission.
In my opinion, this idea of increasing privilege is great except the critical flaw might come later when you refactor your routes or your roles. You'd have to remember everywhere you had > 3 and change it to > 4. If you forget any, that is kind of a security flaw immediately, so I trust you understand my argument there.
Rather than seeing operators like < and >. I would rather see checks for specific roles, like:
if ((req.user.role === 'ADMIN') || (req.user.role === 'MANAGER')) {}
We have to keep coming back to the idea: what is the most simple? Is it simpler to make an isAdmin middleware and then group all your admin routes under the middleware? or is it simpler to put the authorization-check inside each route?
Check this example here:
import isAdmin from '../auth/isAdmin.js'
app.get('/admin', (req, res) => {
if (!isAdmin(req.user)) {
return res.redirect(403, '/error')
}
return res.render('admin')
})
This might be more work, but it's also potentially more fine-grained, so you have more control.
app.get('/foobars', (req, res) => {
if (isAdmin(req.user)) {
return res.json(/* all foobar records from all accounts */)
}
if (isManager(req.user)) {
return res.json(/* all foobar records from the user's account */)
}
return res.json({ error: 'Insufficient privileges for this operation' })
})
My final thought is that, you should have two functions: one checks if the user is authenticated, and one checks if the user is authorized. Then you can stack them together either in a middleware or in two middlewares, or in a route.
I also think you should find a more robust way to check if the user is your self. If you move your app from one computer to another, the user ID might change next time you populate your users table, so id isn't a strong way to latch onto your user.
Related
i was told that using res.locals will decrease the performance of my application , and it's better to attach variables on the request.
in my case i want to attach variables that are accessible only on the server side , and i don't want it to be sent back to the user , and i also came across sending the variable using
next(value) , what is the best approach for my case??
i have this middleware that gets the id of the user from jwt
jwt.verify(
accessToken,
process.env.ACCESS_TOKEN_KEY,
function (err, payload) {
if (err)
return res.status(401).send({
status: "failure",
response: "access token is not valid",
});
id = payload.id;
}
);
res.locals.userId = id;
next();
then this middleware that gets the role of the user based on the id
const RoleId = await sequelize.models.User.findByPk(res.locals.userId);
if (RoleId === 1) {
res.locals.title = "Admin";
next();
} else {
res.locals.title = "Customer";
next();
}
res.locals.xxx will have no different performance from setting res.xxx or req.xxx. No difference at all. So, without a specific reference to whomever said one would be slower than the other that somehow has more context, that's not correct.
And, res.locals are not sent to the client. Template engines doing server-side rendering by convention will look in res.locals to find variables that the template may reference that were not explicitly passed to res.render(). This is very useful for having common data (like a user's name) that you want the template to use, but don't want to have to manually pass to every single res.render() call.
This is entirely under your control since you, on the server, control both the template and the server-side rendering. This allows you to insert things into the page which is then sent to the user, but nothing goes in the template that you don't put there. So, res.locals is not a security risk unless you somehow give an outside agent control over your template (which would be subject to all sorts of security issues beyond just res.locals.
Another advantage of res.locals is that its an independent namespace that is entirely reserved for your use. No variable you use there will conflict with any existing functionality of the http class or Express or whatever web server engine you're using. The res object, on the other hand, has all sorts of existing methods and properties that you have to make sure you don't overwrite. So, res.locals is safer in that regard as it is specifically reserved for your own use. You can put any named property in there without any risk of conflict.
Passing a value to next(value) is how you abort further routing and immediately invoke your error handler, passing value to the error handler. This is not how middleware communicates data to downstream routing or rendering.
Both your code examples look like proper use of res.locals to me. That is exactly what it's for.
On a completely separate topic, your first code example has a coding error in it. You need to put these:
res.locals.userId = id;
next();
Inside the jwt.verify() callback. jwt.verify() is asynchronous. You won't have the id value until that callback is called. It should be like this:
jwt.verify(accessToken, process.env.ACCESS_TOKEN_KEY, function(err, payload) {
if (err) {
return res.status(401).send({
status: "failure",
response: "access token is not valid",
});
}
res.locals.userId = payload.id;
next();
});
Hi please can anyone help me. I want user to be able to access only what they are permitted to access.
I have been looking at several Access Control List packages. I have not made a final decision.
A restaurant which would have several levels of permission.
The customer can place several orders and can see what foods he has ordered
He can also modify the order only within a specified time period e.g. before the order is being processed.
The customer can only view his own order and the stage which the order is.
A staff can only check the order than is under his menu and state how much the order would cost and how long the order would take.
Another staff would be in charge of the stores and how things goes in and goes out.
A Staff can be in charge of a department and at the same time allow input to a menu which is under another department.
I have been looking at how I can go about putting this into Express.js and mongodb
I have looked at the following
https://github.com/optimalbits/node_acl main focus
https://www.npmjs.com/package/acl
https://www.npmjs.com/package/express-acl
But I have not got the granularity and the mix which I stated above.
The permission would be based majorly on data. It has been a little confusing as to how I can go about that.
Any help will be useful
I use mongoose as my driver
As I said in my comments, this design has a bit of business logic that might make it not the best fit for regular ACL-type security controls. On the surface, it seems like the easier to figure out solution would be to just implement your business rules in your Mongoose models or Controller code, depending on your preference. That said, a key piece of doing any of this with an ACL-like approach comes down to your URL design. For example, it's tempting to make your API such that all orders are available through /api/orders and maybe a person would query their own orders via /api/orders?userId=12345. But that makes most ACL-based approaches fail. Instead you have to think about the API in terms of the hierarchy as you want it secured (regardless of if all orders are stored in the Orders Mongoose model, and persist in the orders collection).
So using your first requirement as an example
The customer can place several orders and can see what foods he has ordered
The focus here is that you are securing things by the customer 'owner' of the orders, so to secure it that way you need to setup your route that way, eg (assuming you're using the first middleware you asked about):
app.post('/api/customer/:customerId/orders', acl.middleware(), (req, res, next) => {
const order = new Order(req.body); // TODO: whitelist what info you take in here
order.customerId = req.user.id; // assuming you have a logged-in user that does this
order.save(e => {
if (e) return next(e);
return res.status(201).send(order);
});
});
To support this, you'd register your ACL info as such:
acl.allow('12345', '/api/customer/12345/orders', ['post']);
Minimally, you'd do that. You would likely provide more options such as 'get', etc. As you can guess, this means that you'll need to register permissions for individual users whenever you create them (to support the concept of 'ownership').
For your second requirement,
He can also modify the order only within a specified time period e.g. before the order is being processed.
Despite what I said before, you could arguably do this in an ACL if you really wanted to. For example, you could make the URL account for the status, like '/api/customers/12345/orders/modifiable/6789', but that becomes hard to maintain in my experience. You're better off putting that logic in the controller or the Mongoose logic. It's probably simpler to do it in the controller, unless you plan on using your Mongoose models outside of the Express app. Something like this (note, not using the ACL in this case, though you could if you wanted):
app.param('orderId', (req, res, next, id) => {
Order.findById(id, (err, order) => {
if (err) return next(err);
if (order) {
req.order = order;
return next();
}
const notFound = new Error('Order not found');
notFound.status = 404;
return next(notFound);
});
});
app.put('/api/orders/:orderId', (req, res, next) => {
if (req.order.status !== 'pending') {// or whatever your code setup is
const notProcessable = new Error('Cannot modify an order in process');
notProcessable.status = 422;
return next(notProcessable);
}
// handle the modification and save stuff
});
I'm using koa2 and koa-router together with sequelize on top. I want to be able to control user access based on their roles in the database, and it's been working somewhat so far. I made my own RBAC implementation, but I'm having some trouble.
I need to quit execution BEFORE any endpoint is hit if the user doesn't have access, considering endpoints can do any action (like inserting a new item etc.). This makes perfect sense, I realize I could potentially use transactions with Sequelize, but I find that would add more overhead and deadline is closing in.
My implementation so far looks somewhat like the following:
// initialize.js
initalizeRoutes()
initializeServerMiddleware()
Server middleware is registered after routes.
// function initializeRoutes
app.router = require('koa-router')
app.router.use('*', access_control(app))
require('./routes_init')
routes_init just runs a function which recursively parses a folder and imports all middleware definitions.
// function initializeServerMiddleware
// blah blah bunch of middleware
app.server.use(app.router.routes()).use(app.router.allowedMethods())
This is just regular koa-router.
However, the issue arises in access_control.
I have one file (access_control_definitions.js) where I specify named routes, their respective sequelize model name, and what rules exists for the route. (e.g. what role, if the owner is able to access their own resource...) I calculate whether the requester owns a resource by a route param (e.g. resource ID is ctx.params.id). However, in this implementation, params don't seem to be parsed. I don't think it's right that I have to manually parse the params before koa-router does it. Is anyone able to identify a better way based on this that would solve ctx.params not being filled with the actual named parameter?
edit: I also created a GitHub issue for this, considering it seems to me like there's some funny business going on.
So if you look at router.js
layerChain = matchedLayers.reduce(function(memo, layer) {
memo.push(function(ctx, next) {
ctx.captures = layer.captures(path, ctx.captures);
ctx.params = layer.params(path, ctx.captures, ctx.params);
ctx.routerName = layer.name;
return next();
});
return memo.concat(layer.stack);
}, []);
return compose(layerChain)(ctx, next);
What it does is that for every route function that you have, it add its own capturing layer to generate the params
Now this actually does make sense because you can have two middleware for same url with different parameters
router.use('/abc/:did', (ctx, next) => {
// ctx.router available
console.log('my request came here too', ctx.params.did)
if (next)
next();
});
router.get('/abc/:id', (ctx, next) => {
console.log('my request came here', ctx.params.id)
});
Now for the first handler a parameter id makes no sense and for the second one parameter did doesn't make any sense. Which means these parameters are specific to a handler and only make sense inside the handler. That is why it makes sense to not have the params that you expect to be there. I don't think it is a bug
And since you already found the workaround
const fromRouteId = pathToRegexp(ctx._matchedRoute).exec(ctx.captures[0])
You should use the same. Or a better one might be
var lastMatch = ctx.matched[ctx.matched.length-1];
params = lastMatch.params(ctx.originalUrl, lastMatch.captures(ctx.originalUrl), {})
All:
I am pretty new to Epxress, I build a middleware to check user credential, and I specify it like:
var check = function(req, res, next){/* checking user cred*/}
And I use it like:
app.use(check);
OR like:
app.get("some url", check, function(req, res, next){})
But there is only one thing confuses me, sometimes, I need to skip the check in same handler depends on req.query, I wonder if there is a way(or design pattern) to do this without specify this condition checking inside check middleware( I just want to make check modulized and focus on its biz logic)?
Thanks
If you're looking to modularize the check middleware so you can use it elsewhere, you could pretty easily include some sort of flag to check against the req.query parameter or whatever else you'd want to check and make it more generic:
function(req, res, next){
if(!req.query){
// do some kind of check
} else {
// do a check with req.query
}
}
Is that along the lines of what you are trying to do?
For authentication, a typical pattern would be to have a collection of routes that allow anonymous access, and a section that requires authentication, thus calls your middleware.
Additionally, look into the Passport library for your authentication concerns, it integrates really well into express.
I would like to create kind of a before filter which allows me to make the current user available in all actions. The followint approach works well and I didn't even need to declare a global variable:
app.use(function(req, res, next){
if(req.session.user_id){
/* Get user from database
and share it in a variable
that can be accessed frooom ...
*/
User.find({ /* ... */ }, function(err, users){
if(users.length == 1){
req.current_user = users[0];
}
next();
});
}
else{
next();
}
});
app.get('/', function(req, res){
// ... here!!
console.log(req.current_user);
res.render('index', {
current_user: req.current_user,
});
});
But I'm still unsure if it is okay to manipulate req because I don't know if it's right to change something that's not owned by me? Is there a better way to do this?
Go right ahead and tack on properties to req! When I was first starting out with Node.js and JavaScript, this felt very odd to me too (coming from a predominately C++ background). It is, however, quite natural given JavaScript's prototypical object model. After you get comfortable with it, you'll realize that you can do powerful things in succinct code.
I'm the developer of Passport (mentioned by the previous commenter). If you are planning on developing middleware that can be reused across apps, my advice is to pay a bit of attention to how you name the properties that you add to req or res, to avoid any potential conflict with other middleware in the same application.
For example, Passport sets the user at req.user, but gives an option to change that (so an app can say set it at req.currentUser, for example.). Internal, private variables are attached to a req._passport property.
It's a common approach to extend req with session or user object
For example see these examples:
Passport, a popular authentication library https://github.com/jaredhanson/passport/blob/master/lib/passport/strategies/session.js
Connect middleware for cookie session https://github.com/senchalabs/connect/blob/master/lib/middleware/cookieSession.js