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.
});
Related
I'm building login system for my ChatApp and got stuck on JWT malformed when trying to create a new chatroom. When I login I get token and I try to send request from Postman to create chatroom, I put Token as bearer and in body I set name as my chatroom which is ok to put. Code:
auth.js:
const jwt = require('jwt-then')
module.exports = async (req, res, next) => {
try {
if (!req.headers.authorization) throw 'Forbidden!'
const token = req.headers.authorization.split('')[1]
const payload = await jwt.verify(token, process.env.SECRET)
req.payload = payload
next()
} catch (err){
res.status(401).json({
message: err.message
})
}
}
routes/chatroom.js:
const { catchErrors } = require('../handlers/errorHandlers.js')
const chatroomController = require('../controllers/userControllers.js')
const auth = require('../middlewares/auth')
router.post('/', auth, catchErrors(chatroomController.createChatroom))
module.exports = router
chatroomController:
const Chatroom = mongoose.model('Chatroom')
exports.createChatroom = async (req, res) => {
const {name} = req.body
const nameRegex = /^[A-Za-z\s]+$/
if (!nameRegex.test(name)) throw 'Chatroom name can only contain alphabets.'
const chatroomExists = await Chatroom.findOne({name})
if (chatroomExists) throw 'Chatroom already exists.'
const chatroom = new Chatroom({
name,
})
await chatroom.save()
res.json({
message: 'Chatroom created!',
})
}
Please help me
I am using passport-jwt strategy, JWT in cookies to authenticate a user. The user authentication is working fine. However, at the endpoints where we need not check for authenticated user to grant access, I want to check is a user is logged in. If yes, I'll hide some options from the webpage and show others. I am trying to implement this by retrieving jwt from cookie, verifying it and finding the corresponding user and returning the user obtained.
Naturally, I want the user to be returned in User in the router before moving forward.
authenticate.js
var verifyToken = async function verifyToken(token, secretkey){
// console.log('This is token: ', token);
// console.log('This is secretkey: ', secretkey);
try{
var data = jwt.verify(token, secretkey);
return data;
} catch(err){
return err;
}
}
exports.loggedIn = async function loggedIn(req){
try{
var token = req.signedCookies.jwt;
console.log(token);
var userFound = false;
if(token){
data = await verifyToken(token, config.secretKey);
user = await getUser(data);
console.log('Data: ',data);
console.log('User: ', user);
}
else
return userFound;
}
catch(error){
console.log(err);
}
}
var getUser = function getUser(jwt_payload){
try{
var query = User.findOne({_id: jwt_payload._id});
var returnUser = query.exec();
return returnUser;
}
catch(err){
console.log(err);
}
}
However in the router, the next line i.e. console.log('Index router: ',User); is executed before User is obtained, printing Index router: Promise {<pending>}.
router.get('/', function(req, res, next) {
Posts.find({})
.populate('posted_by', 'username _id')
.then((allPosts) => {
return allPosts;
})
.then((list) => {
console.log("list: ",list);
if(list.length > 10)
list = list.splice(10, list.length-10);
res.statusCode = 200;
res.setHeader('Content-Type','text/html');
var User = authenticate.loggedIn(req); //Here I want the User before moving forward
console.log('Index router: ',User);
res.render('index', {
posts: list,
user: User
});
})
.catch((err) => next(err));
});
I have tried writing a different async function in which I as follows async function getuser(){ User = await authenticate.loggedIn(req); }, but the same problem occurs even then. Please help!
You have to use await:
[...]
var User = await authenticate.loggedIn(req);
[...]
Of course set the function as async:
router.get('/', async function(req, res, next) {
[...]
I have a express nodejs backend which has three URL functions in which
1) registerUser() added user details to database and provided a JWT for the caller
2) verifyToken()- verifies if the JWT is valid
3) getConfiguration()- if JWT is verified from above function provides user with some configuration data
So the express code I'm using to achieve this is
//Routes.js
app.use(requestIp.mw())
app.route('/register')
.post(userController.registerUser);
app.use(userController.verifyToken)
app.route('/user/configuration')
.post(chayakkadaController.getConfiguration);
Now my issue is whenever I try calling the URL /register instead of calling registerUser function it calls verifyToken and says my token is invalid ( I want registerUser function to work without token, but getConfiguration should work only with token)
This is my verifyToken function
export function verifyToken(req, res, next) {
var token = req.body.token || req.headers["token"];
var appData = {};
if (token) {
jwt.verify(token, process.env.SECRET_KEY, function (err, decoded) {
if (err) {
appData["status"] = 1;
appData["error"] = "Invalid Token";
res.status(500).json(appData);
} else {
req.user = decoded;
next();
}
});
} else {
appData["status"] = 1;
appData["error"] = "Need access token";
res.status(403).json(appData);
}
}
My register User code
export function registerUser(req, res) {
let userData = {
device: req.body.device,
device_version: req.body.device_version,
device_id: req.body.device_id,
app_version: req.body.app_version,
app_id: 2,
ip_address: req.headers['x-real-ip'] || req.connection.remoteAddress
}
database.query(`INSERT INTO users SET ?`, userData)
.then(result => {
let user = {
id: result.insertId
}
let token = jwt.sign(user, process.env.SECRET_KEY);
let appData = {};
appData["token"] = token;
redis.sendMessage({
qname: 'registration_queue',
message: result.insertId + '',
}, (err, resp) => {
res.status(201).json(appData);
});
})
.catch(err => {
console.log(err);
res.status(500).json("Database Error");
})
}
Why you wanna to invent the wheel? there is a NPM module for that:
express-jwt
It has middleware that checks the jwt, if it valid, it decodes the payload and adds it to the request after that it proceed to your controller, if it is not valid, it throws an error, that you should catch, and do what ever you want.
It has the unless feature, so you can configure the entire subpath as restricted unless it is /register
router.use(`admin/`, [
expressJwt({ secret: jwtSecret }).unless({
path: ['/register]
}),
]);
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"})
})
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();
}