Skip to content

Instantly share code, notes, and snippets.

@joshnuss
Last active May 17, 2022
Embed
What would you like to do?
Express.js role-based permissions middleware
// the main app file
import express from "express";
import loadDb from "./loadDb"; // dummy middleware to load db (sets request.db)
import authenticate from "./authentication"; // middleware for doing authentication
import permit from "./authorization"; // middleware for checking if user's role is permitted to make request
const app = express(),
api = express.Router();
// first middleware will setup db connection
app.use(loadDb);
// authenticate each request
// will set `request.user`
app.use(authenticate);
// setup permission middleware,
// check `request.user.role` and decide if ok to continue
app.use("/api/private", permit("admin"));
app.use(["/api/foo", "/api/bar"], permit("manager", "employee"));
// setup requests handlers
api.get("/private/whatever", (req, res) => res.json({whatever: true}));
api.get("/foo", (req, res) => res.json({currentUser: req.user}));
api.get("/bar", (req, res) => res.json({currentUser: req.user}));
// setup permissions based on HTTP Method
// account creation is public
api.post("/account", (req, res) => res.json({message: "created"}));
// account update & delete (PATCH & DELETE) are only available to managers
api.patch("/account", permit('manager'), (req, res) => res.json({message: "updated"}));
api.delete("/account", permit('manager'), (req, res) => res.json({message: "deleted"}));
// viewing account "GET" available to manager and employee
api.get("/account", permit('manager', 'employee'), (req, res) => res.json({currentUser: req.user}));
// mount api router
app.use("/api", api);
// start 'er up
app.listen(process.env.PORT || 3000);
// middleware for authentication
export default async function authorize(request, _response, next) {
const apiToken = request.headers['x-api-token'];
// set user on-success
request.user = await request.db.users.findByApiKey(apiToken);
// always continue to next middleware
next();
}
// middleware for doing role-based permissions
export default function permit(...permittedRoles) {
// return a middleware
return (request, response, next) => {
const { user } = request
if (user && permittedRoles.includes(user.role)) {
next(); // role is allowed, so continue on the next middleware
} else {
response.status(403).json({message: "Forbidden"}); // user is forbidden
}
}
}
// dummy middleware for db (set's request.db)
export default function loadDb(request, _response, next) {
// dummy db
request.db = {
users: {
findByApiKey: async token => {
switch {
case (token == '1234') {
return {role: 'manager', id: 1234};
case (token == '5678') {
return {role: 'employee', id: 5678};
default:
return null; // no user
}
}
}
};
next();
}
@joshnuss
Copy link
Author

joshnuss commented Nov 21, 2018

@sukjae you would need to adjust the code a bit, but a similar approach work on a document level.

The essence of this approach is a middleware that checks permissions and can block access to rest of the call chain.

In a collection situation the flow is:

  1. Load database
  2. Load user/role data
  3. Check permission
  4. Fetch & respond

But in your case you can fetch the object before checking permission. For example:

  1. Load database
  2. Load user/role data
  3. Fetch object from db
  4. Check permission
  5. Respond

Just store the object on the request, ie request.project = project, and update middleware to check if (request.project.owner == request.user) { ... }

Sorry for slow reply, github doesn't send email notifications for gists

@pedrovinicius
Copy link

pedrovinicius commented Jan 25, 2019

It's exactly what I was looking for! Thank you so much!

@ankii9600
Copy link

ankii9600 commented Apr 10, 2019

Extremely helpful .. saved my 4-5 working hours

@iSanjayAchar
Copy link

iSanjayAchar commented Jun 9, 2019

Extremely helpful, thank you so much

@kingRayhan
Copy link

kingRayhan commented Jul 28, 2019

Thanks you so much

@switchover
Copy link

switchover commented Jul 31, 2019

Thanks.. it's simple but powerful! 👍

@HamzaKazmi43
Copy link

HamzaKazmi43 commented Aug 1, 2019

...allowed can you explain this to me i am new at this

@joshnuss
Copy link
Author

joshnuss commented Aug 1, 2019

@HamzaKazmi43 f(...args) is called "rest parameters". It allows the function to consume the parameters as if it was an array.
For more info, see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters

@xuxucode
Copy link

xuxucode commented Aug 23, 2019

very useful, thank you!

@mosmartin
Copy link

mosmartin commented Sep 15, 2019

Thank you @joshnuss, this is brilliant!

@staminna
Copy link

staminna commented Sep 28, 2019

Can I see some example usage of this code?
I also tried to replace 'api' with app and I get some unexpected circular error of some kind. I am not using router directly. I am only using app=express();

@yourstruly22
Copy link

yourstruly22 commented Oct 10, 2019

Thanks man, this just saved me hours of work today!

@xocialize
Copy link

xocialize commented Oct 19, 2019

Ditto on this being very helpful. And I learned about rest parameters so that was a big bonus for me.

Thanks for sharing.

@jose-matias
Copy link

jose-matias commented Jan 14, 2020

Thank you for sharing.

@marlonfsolis
Copy link

marlonfsolis commented May 12, 2020

Thank you! This was extremely helpful for me.

@Juanmadepalacios
Copy link

Juanmadepalacios commented Jun 10, 2020

thanks! save me"

@nzartre
Copy link

nzartre commented Jul 3, 2020

This is helpful. I have adapted mine based on your work. Thanks!

@Dahkenangnon
Copy link

Dahkenangnon commented Jul 10, 2020

Thank

@jalakpatoliya
Copy link

jalakpatoliya commented Jul 30, 2020

Just here to Thank... awsome buddy thanks 👊️😎️

@Pipoupi
Copy link

Pipoupi commented Jul 31, 2020

Here to thank you too ! :)

@anurag-tiwari-builtio
Copy link

anurag-tiwari-builtio commented Aug 5, 2020

This is great, was looking for something similar.

Thanks man

@Hyllesen
Copy link

Hyllesen commented Aug 16, 2020

I looked at other implementations but this is the most simple and useful one so far

@Kibria7533
Copy link

Kibria7533 commented Aug 24, 2020

@hardik-itoneclick
Copy link

hardik-itoneclick commented Aug 29, 2020

Extremely Useful. Works like a charm.

@TheBrown
Copy link

TheBrown commented Oct 14, 2020

Nice, Thank you

@sergey-shpak
Copy link

sergey-shpak commented Jan 9, 2021

By using 'owner' role I would expect the user who created(own) the doc is trying to access, otherwise it is confusing and misleading.
I would suggest to rename 'owner' role to something else, because 'owner' in this pattern is not what you expect.

@skang
Copy link

skang commented Apr 1, 2021

Excellent design - simple, beautiful and powerful. Thank you very much for sharing!

@richardscarrott
Copy link

richardscarrott commented May 8, 2021

@sergey-shpak yeh me too, I think it's just an unfortunately named role?

@katonahmike
Copy link

katonahmike commented May 20, 2021

Very helpful. Thanks for sharing.

@Cool-Programmer
Copy link

Cool-Programmer commented Aug 7, 2021

This is perfect, thank you a ton.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment