Skip to content

Instantly share code, notes, and snippets.

Last active May 17, 2022
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
// authenticate each request
// will set `request.user`
// 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"/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
// 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};
return null; // no user
Copy link

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

Copy link

pedrovinicius commented Jan 25, 2019

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

Copy link

ankii9600 commented Apr 10, 2019

Extremely helpful .. saved my 4-5 working hours

Copy link

iSanjayAchar commented Jun 9, 2019

Extremely helpful, thank you so much

Copy link

kingRayhan commented Jul 28, 2019

Thanks you so much

Copy link

switchover commented Jul 31, 2019

Thanks.. it's simple but powerful! 👍

Copy link

HamzaKazmi43 commented Aug 1, 2019

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

Copy link

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:

Copy link

xuxucode commented Aug 23, 2019

very useful, thank you!

Copy link

mosmartin commented Sep 15, 2019

Thank you @joshnuss, this is brilliant!

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();

Copy link

yourstruly22 commented Oct 10, 2019

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

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.

Copy link

jose-matias commented Jan 14, 2020

Thank you for sharing.

Copy link

marlonfsolis commented May 12, 2020

Thank you! This was extremely helpful for me.

Copy link

Juanmadepalacios commented Jun 10, 2020

thanks! save me"

Copy link

nzartre commented Jul 3, 2020

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

Copy link

Dahkenangnon commented Jul 10, 2020


Copy link

jalakpatoliya commented Jul 30, 2020

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

Copy link

Pipoupi commented Jul 31, 2020

Here to thank you too ! :)

Copy link

anurag-tiwari-builtio commented Aug 5, 2020

This is great, was looking for something similar.

Thanks man

Copy link

Hyllesen commented Aug 16, 2020

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

Copy link

Kibria7533 commented Aug 24, 2020

Copy link

hardik-itoneclick commented Aug 29, 2020

Extremely Useful. Works like a charm.

Copy link

TheBrown commented Oct 14, 2020

Nice, Thank you

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.

Copy link

skang commented Apr 1, 2021

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

Copy link

richardscarrott commented May 8, 2021

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

Copy link

katonahmike commented May 20, 2021

Very helpful. Thanks for sharing.

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