Role based authorisation for Node js or Express js - javascript

Is there any library for role-based authorization in node.js or Express js? Like: Super Admin, Admin, Editor, User etc.

You can use role based middleware. thanks to joshnuss.
I am using it for my api having different users like developers, customers, employee, admin.
works like a charm
index.js
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 "./permission"; // 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("owner", "employee"));
// setup requests handlers
api.get("/private/whatever", (req, res) => response.json({whatever: true}));
api.get("/foo", (req, res) => response.json({currentUser: req.user}));
api.get("/bar", (req, res) => response.json({currentUser: req.user}));
// setup permissions based on HTTP Method
// account creation is public
api.post("/account", (req, res) => req.json({message: "created"}));
// account update & delete (PATCH & DELETE) are only available to account owner
api.patch("/account", permit('owner'), (req, res) => req.json({message: "updated"}));
api.delete("/account", permit('owner'), (req, res) => req.json({message: "deleted"}));
// viewing account "GET" available to account owner and account member
api.get("/account", permit('owner', 'employee'), (req, res) => req.json({currentUser: request.user}));
// mount api router
app.use("/api", api);
// start 'er up
app.listen(process.env.PORT || 3000);
// middleware for doing role-based permissions
export default function permit(...allowed) {
const isAllowed = role => allowed.indexOf(role) > -1;
// return a middleware
return (req, res, next) => {
if (req.user && isAllowed(req.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(req, res, next) {
// dummy db
request.db = {
users: {
findByApiKey: async token => {
switch {
case (token == '1234') {
return {role: 'superAdmin', id: 1234};
case (token == '5678') {
return {role: 'admin', id: 5678};
case (token == '1256') {
return {role: 'editor', id: 1256};
case (token == '5621') {
return {role: 'user', id: 5621};
default:
return null; // no user
}
}
}
};
next();
}
middleware for authentication
export default async function authorize(req, res, next) {
const apiToken = req.headers['x-api-token'];
// set user on-success
request.user = await req.db.users.findByApiKey(apiToken);
// always continue to next middleware
next();
}

Related

How to create user group security for REST API using middleware

In order to secure REST API I'm using middleware to check for user's JWT token and only allow that particular user to access his own data.
In auth.js
const jwt = require('jsonwebtoken')
const User = require('../models/user')
const auth = async (req, res, next) => {
try {
const token = req.header('Authorization').replace('Bearer ', '')
const decoded = jwt.verify(token, process.env.JWT_SECRET)
const user = await User.findOne({ _id: decoded._id, 'tokens.token': token })
if (!user) { // If no user is found
throw new Error()
}
// if there's a user
req.token = token
req.user = user
next()
} catch (e) {
res.status(401).send({ error: 'Please authenticate.' })
}
}
module.exports = auth
In one of the get/update router
router.get('/itemShipmentStatus', auth, async (req, res) => {
// Get the items shipment status from db.
})
However, I've noticed I need to create a new admin user (e.g. admin 1, admin2) to get and update the itemShipmentStatus for all the users. Is there a way to achieve user group authentication through the middleware (auth.js?)
Update:
The only solution I can think of is to add another "userGroup" field to the user document when creating a new user. Then in the middleware auth.js add in another condition to check if the user belongs to the admin group.
if (!user || user.userGroup !== 'Admin') { // If no user is found
throw new Error()
}
Is this the conventional way of doing it?
I would suggest adding permissions array stored in the user. That way you are more flexible. Then
const auth = (allowedPermission) => (async (req, res, next) => {
try {
const token = req.header('Authorization').replace('Bearer ', '')
const decoded = jwt.verify(token, process.env.JWT_SECRET)
const user = await User.findOne({ _id: decoded._id, 'tokens.token': token })
if (!user) { // If no user is found
throw new Error()
}
if (!user.permissions.includes(allowedPermission)){
throw new Error() // Forbidden 403
}
// if there's a user
req.token = token
req.user = user
next()
} catch (e) {
res.status(401).send({ error: 'Please authenticate.' })
}
})
and in the route
router.get('/itemShipmentStatus', auth([admin, user]), async (req, res) => {
// Get the items shipment status from db.
})
Then it would be a matter to identify the correct code to run.
I would suggest considering the division of the api. A public api and an admin api. This is because conceptually a user may want to be admin and access its own itemShipmentStatus. So having
router.get('/itemShipmentStatus', auth([admin, user]), async (req, res) => {
// Get the items shipment status from db.
})
router.get('/admin/itemShipmentStatus', auth([admin]), async (req, res) => {
// Get the items shipment status from db of all user.
})
This allows an admin user to test the API as a normal user and get all the status as an admin.
A more conventional way of doing this would be to create an AuthRouter which extends the default express.Router and checks for allowed roles, so there will be no need to use middleware for each route.
Extending express.Router to check for roles:
const express = require('express');
const jwt = require('jsonwebtoken');
const User = require('../models/user');
export default class AuthRouter extends express.Router {
addRoles(...roles) {
return this.use(checkAccessForRoles(...roles));
}
}
const checkAccessForRoles = (...roles) => async (req, res, next) => {
const token = req.header('Authorization').replace('Bearer ', '');
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findOne({ _id: decoded._id, 'tokens.token': token });
if (!roles.includes(user.role)) throw new Error('Forbidden');
req.user = user;
return next();
};
Using AuthRouter for ADMIN user role:
const adminRouter = new AuthRouter({
prefix: '/admin',
})
.addRoles(['ADMIN']);
adminRouter.get('/itemShipmentStatus', async (req, res) => {
// Get the items shipment status from db.
});

How to pass parameter to function in middleware

So I wanted to pass a parameter to a middleware but I'm struggling on some points.
I have that function in the routes' handler:
router.get('/users', auth.required, userController.findAll);
Then it would go to the auth function which calls getTokenFromHeaders:
const auth = {
required: jwt({
secret: 'secret',
userProperty: 'payload',
getToken: getTokenFromHeaders,
}),
...
};
In getTokenFromHeaders function, the token is retrieved and checked, it looks like that:
const getTokenFromHeaders = (req) => {
...
return token; // Or null in case it's not there or incorrect
So my goal would be to pass a parameter like that auth.required('role') to check the user's role inside getTokenFromHeaders function (defining more auth functions would be fine as well (auth.admin, auth.whatever, ...)
I already tried modifying it as the following:
const auth = {
required: jwt({
secret: 'secret',
userProperty: 'payload',
getToken: getTokenFromHeaders(req, res, role),
}),
But it says that req and res is not defined.
Is there any way to do that?
get method receives a path and 2 callbacks where first one is a middleware:
router.get('/users', auth, userController.findAll);
Middleware is like findAll function and is executed before:
var auth = function (req, res) {
// Get token from header (http://expressjs.com/en/api.html#req.get)
var token = req.get("myToken");
// TODO Validate token:
var isValid = someFunction(token);
if (!isValid) {
// ************
// TODO Check if user can access this resource
// ************
res.json("not authorized");
}
// Go to findAll (next callback)
};
I suggest you take at look in docs specially in app.use to understand better how middleware works in express.
I think that :
1) Call of request
app.use('/users', required);
2) Check authentification
const required = (req, res, next) => {
const auth = {
required: jwt({
secret: 'secret',
userProperty: 'payload',
getToken: getTokenFromHeaders(req),
}),
...
};
if (auth.required) next();
else // error
}
3) Next step
router.get('/users', userController.findAll);
4) userController.findAll
userController.findAll will send the response with with the parameters recovered (req and res)

Big problem with security (JWT NodeJS), one token for all acces

I have a really big problem with security in my web application.
I implemented JWT token when user login to my application (REST API returns token).
In my jwt token, I have only userID. Problem is that, when I would like to login on user with ID = 1,
I can see and execute rest actions from all other users with the same token. for example:
When I looged userId = 1, I doing GET action: /api/users/1 and I have a information about user 1. But I can doing action /api/users/2, 3 etc.
All with one token. how to secure it?
const jwt = require('jsonwebtoken');
const env = require('../config/env.config.js');
module.exports = (req, res, next) => {
try {
const token = req.headers.authorization.split(' ')[1];
const decoded = jwt.verify(token, env.SECRET_KEY);
req.userData = decoded;
next();
} catch (error) {
return res.status(401).json({
message: 'Auth failed',
});
}
};
I think the best solution would be to create middleware that check the id of the sender and attach it to routes, similar to bellow
const middleware = (req, res, next) => {
const id = req.params.id || req.body.id || req.query.id
if (req.userData.id === id) {
next()
} else {
res.status(403).send({message: "forbidden"})
}
}
router.get("/api/users/:id", middleware, (req, res) => {
// do your staff
res.send({message: "ok"})
})
router.put("/api/users/:id", middleware, (req, res) => {
// do your staff
res.send({message: "ok"})
})

from fetch api in react to express res.redirect

I searched for a long time, but I could not find the answer.
When someone requests data from an api, fetch or ajax ( in SPA react )
I want to send data to only the logged in or authenticated user,
if not logged user or not authenticated user,
I would like to redirect to 'someReAuthPage'
My strategy is as follows.
in SPA react client
fetch('/api/someData', {
method : "GET",
})
.then(......)
in express server
app.get('/api/:blah', (req, res, next) => {
if(logged in or authenticated user){
next()
} else {
res.redirect('someReAuthPage')
}
})
app.get('/api/someData', (req, res) => {
..........
res.json('someJsonData')
}
but this code not working
res.redirect not working....
Do I have to write a redirect conditional statement for every fetch api?
Is there a way to redirect directly from the server without using conditional statement in client fetch api???
somebody help me ...
Write a middleware function. Here's my implementation for checking if the user is logged in in an ecosystem where I use Firebase.
// Verify the user identity
const verifyoAuth = (req, res, next) => {
const idToken = req.token || ''; // I get the token using BearerToken parser.
if( idToken == '' ){
return returnQueryError(res, 401, "Unauthorized");
}
fbAdmin.auth().verifyIdToken(idToken).then( user => {
req.user = user;
next(); // resume to next route.
}).catch( error => {
// Report the incident
eventsLogger.error( error );
// Send back 401 for usr
returnQueryError(res, 401, "Unauthorized");
});
}
And to use it with a particular route:
app.get('/api/:blah', verifyoAuth, (req, res, next) => {
const { user } = req; // the `user` object would have some data about the authenticated user.
// Return what data
res.json({ msg: 'I am authenticated' })
})

req.user is undefined when using PassportJS, SequelizeJS and JWT tokens

I already checked multiple answers here on Stackoverflow, and also went through on the documentation but I still cannot find out what could be the problem. In my application I'm using SequelizeJS to access to my mySQL database and now I'm trying to secure my REST API endpoints with PassportJS using the JWT Strategy.
./app.js
// ...
// passport
app.use(passport.initialize());
require('./config/passport')(passport);
// ...
./config/passport.js
var passport = require('passport');
var passportJwt = require('passport-jwt');
var models = require('../models');
var config = require('./config');
var ExtractJwt = passportJwt.ExtractJwt;
var Strategy = passportJwt.Strategy;
module.exports = function(passport) {
var params = {
secretOrKey: config.jwt.secret,
jwtFromRequest: ExtractJwt.fromAuthHeader()
};
passport.use(new Strategy(params, function(jwt_payload, done) {
models.User.findOne({
where: {
id: jwt_payload.id
}
}).then(
function(user) {
if (user) {
done(null, user);
} else {
done(null, false);
}
},
function(err) {
return done(err, false);
}
);
}));
};
I'm trying to get the user entity from the request of this simple route:
var router = express.Router();
// ...
router.route('/user/me', passport.authenticate('jwt', { session: false }))
.get(function(req, res) {
console.log(req.user);
res.json(req.user);
});
I already created another route which returns a JWT token based on the provided username and password. When I call the /user/me endpoint I attach the JWT token into the header, for example:
Authorization: JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MX0.M9z3iWNdjAu4THyCYp3Oi3GOWfRJNCYNUcXOw1Gd1Mo
So, my problem is that when I call the /user/me endpoint with a token, the req.user will be undefined and I cannot figure it out what is the reason.
Thank you in advance for your help!
Your route definition seems to be wrong: router.route doesn't accept a middleware in its second argument, so authentication does not happen at all.
It should be smth like
var router = express.Router();
// ...
router.route('/user/me')
.all(passport.authenticate('jwt', { session: false }))
.get(function(req, res) {
console.log(req.user);
res.json(req.user);
});

Categories