Authorization middleware JWT confusion - node.js

I have a suspicion about the relative security of some authentication middleware code I came across in a course im enrolled in.
So I used postman to send a request to a protected route(see route code below) and found that I was able retrieve an order for one user with a token generated for another user.
const protected = asyncHandler(async (req, res, next) => {
let token;
if (
req.headers.authorization &&
req.headers.authorization.startsWith("Bearer")
) {
try {
token = req.headers.authorization.split(" ")[1];
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = await User.findById(decoded.id).select("-password");
next();
} catch (error) {
console.error(error);
res.status(401);
throw new Error("Not authorized, token failed");
}
}
if (!token) {
res.status(401);
throw new Error("Not authorized, No token found");
}
});
export protected
It seems evident to me that this middleware code will only verify if a user from decoded token exists in the DB and but will not limit access to resources based on the user/token.
import {addOrderItems, getOrderbyId} from "../controllers/orderController.js";
import { protected } from "../middleware/authMiddleware.js";
const router = express.Router();
router.route("/").post(protected, addOrderItems);
router.route("/:id").get(protected, getOrderbyId);
//:id is the order id
However, when testing another protected route for updating a user's profile info, I receive an error when using wrong token.
Was hoping for some clarification

jwt.verify will only verify that the given token is generated by the server or not. It doesn't care which user send this token.
For your protected middleware, it just check if the request is authorized. If so, the request will pass to the controller.
As for the updating route. It probably be something like this:
// route
router.route("/:userId", protected, updateController)
const updateController = (req, res) => {
const user = req.user; // this is the one generated by protected middleware
const reqUserId = req.params.userId; // this is the one send by request
if (user.id !== reqUserId) {
// if two ids are not the same, it means someone is trying
// to update the profile with the wrong token
res.status(401);
}
// update profile in database
}

Related

How to perform an api call from a middleware in express - NodeJS

First of all, I'm working with express in NodeJS.
I want to create an API call for updating the user's personal account informations.
Before doing this I should ask the user for the password for more security, this operation will be handled in this middleware isPasswordCorrect inside the request:
const isPasswordCorrect = (req, res, next) => {
const password = req.password
// perform the login call, to confirm the user identity
// if password is correct call next()
}
I already created this endpoint to log in:
router.post('/login', (req, res) => {
const { login, password } = req.body
// do some logic for checking if the login data are correct
res.json({"accessToken": accessToken})
})
So to facilitate the task, I want to call the above-mentionned login endpoint inside the middleware isPasswordCorrect, to check the identity of the user before updating his data
I would not do such an "internal API call", it will cause coupling between the APIs. As a result, the code is difficult to maintain.
I would create a repository or service layer to confirm the user identity.
E.g.
user-service.js
export const identifyUser = (password) => {
// confirm the user identity
// throw new Error('user identity error');
return password
}
Then, in your middleware
const isPasswordCorrect = (req, res, next) => {
try {
userService.identifyUser(req.password)
next()
} catch (e) {
next(e);
}
}

Delete User and Logout that user from all Devices

I wanted to implement a feature in my app. Where an Admin can delete the user. So basically the delete is working fine but somehow i cannot logout the logged in user. Let me explain it more briefly, Suppose there is a User A which is currently using my app and the admin decided to remove that user from the app so they can't no longer access the features of the app. To remove the user i can call an API and delete that user but if i completely delete the user it loses all the access to the API's call coz user with the certain ID isn't available anymore and the app breaks coz the API call will fail for that deleted User. So I was wondering is there anyway to logout the user after admin deletes it.
The Frontend is on ReactJs and Backend is on NodeJs. And i am using JWT for authentication. Any help will be appreciated and if this question isn't clear enough please let me know so i can explain it more.
In backend in every protected route you should verify the token and token should contain user id or email using that you will verify the token. After deleting the user throw error with no user found and in frontend make sure if there are the error no user found then it will delete the JWT token.
What comes into my mind is to put a middleware between your requests and server. By doing so, instead of trying to log out from all devices, we will not allow any action if user does not exist; in this very example, we will prevent the user to delete a place and toast a message on the front end. I will share an example of that, but you need to tweak the code according to your needs.
Http Error Model
class HttpError extends Error {
constructor(message, errorCode) {
super(message);
this.code = errorCode;
}
}
module.exports = HttpError;
Middleware
const HttpError = require('../models/http-error');
module.exports = (req, res, next) => {
try {
// Check user if exists
User.findById(req.userData.userId).exec(function (error, user) {
if (error) {
throw new Error('Authentication failed!');
}
else {
return next();
}
});
}
catch (error) {
return next(new HttpError('Authentication failed!', 403));
}
};
Route
const express = require('express');
const router = express.Router();
const checkAuth = require('../middleware/check-auth');
router.use(checkAuth);
// Put after any routes that you want the user to be logged in
router.delete('/:placeId', placesControllers.deletePlace); //e.x.
...
module.exports = router;
E.x. controller (with MongoDB)
const deletePlace = async (req, res, next) => {
const placeId = req.params.placeId;
let foundPlace;
try {
foundPlace = await Place.findById(placeId).populate('userId').exec();
}
catch (error) {
return next(new HttpError('Could not find the place, please try again', 500));
}
// Delete place
res.status(200).json({message: 'Deleted place'});
};
FRONT END PART
import toastr from 'toastr';
....
try {
const response = await fetch(url, {method, body, headers});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message);
}
}
catch(error) {
// handle the error, user not found
console.log(error.message);
toastr.error(error.message, 'Error', {
closeButton: true,
positionClass: 'toast-top-right',
timeOut: 2000,
extendedTimeOut: 1,
});
}

How to pass the req.user with jwt to main.handlebars to show user is globally logged in?

I am using express, node, handlebars, jwt, auth middleware, and NOT using passport (I see this in a lot of the questions about this topic, I can't use that here).
In the navbar of the webpage, I want to have the current logged-in user's information available (via req.user, presumably). There are certain routes that do not require a user to be logged in. Whether that is required or not, I still want the logged-in user's information in the navbar...or if no user is logged in, then nothing in the navbar.
The trouble I have had is when I included router.get('/home', auth, async (req, res, next) => {} then that route is not available without being logged in, thusreq.user is undefined without that. Or is there a way without auth between /home & async?
Seems like the solution is the pass the req.user to main.handlebars somewhere in server.js--that sure would make life easier than passing req.user on each route it is needed.
I am very very new to this, extra effort on what to do will be appreciated. Here is what I have in my auth middleware, if it helps. Thanks!
const config = require('config');
const jwt = require('jsonwebtoken');
const debug = require('debug')('app:auth');
module.exports = (req, res, next) => {
try {
let token = req.cookies.auth_token;
if (!token) {
// debug(req.get('Authorization'));
const authHeader = req.get('Authorization').split(' ');
if (authHeader.length == 2 && authHeader[0] == 'Bearer') {
token = authHeader[1];
}
}
if (!token) {
throw Error('missing token');
}
const secret = config.get('auth.secret');
const payload = jwt.verify(token, secret);
// debug(payload);
req.user = payload;
next();
} catch (err) {
debug(err.stack);
res.redirect('/account/login');
}
};
Each route you have to pass the variable in the render section eg:
res.render('path and your view filename'), {
detailUser: req.user
}
In the navbar, you call it eg
{{detailUser}}
If there's no req.user details, it won't come up.
I have a shopping cart, and I use session and I pass this variable eg:
isCustomer: req.session.customerId
and in my header, I have an if statement eg:
{{#if isCustomer}}
conditional logic for if User is a Customer
{{else}}
conditional logic if not a customer
{{/if}}

Node.js middleware to read more than one mongoose collection

I am new to authentication with node.js and am struggling to implement the following:
I currently have a middleware function that checks the access token sent with the request and pulls the user relating to that token, then appends that user onto the request so I can use their details. This works completely fine for my Users collection, however I am wanting to add a new collection for a completely different type of user, called Owners.
Based on the function I currently have, I cannot seem to find a way to have it check both collections - this is my current function that works with my one Users collection.
//
// Middleware that authenticates token and appends user to request
//
module.exports.required = function (req, res, next) {
const auth_header = req.header("authorization").split(" ");
const auth_type = auth_header[0];
const auth_token = auth_header[1] || null;
// Check token
if (auth_type !== "Bearer" || !auth_token) {
return next(HttpError(401, "Token is invalid."));
}
// Find user matching access token
return User.findOne({ access_token: auth_token })
.orFail(HttpError(401, "Token does not exist."))
.then((user) => {
try {
// Check if token has no expired
decodeToken(auth_token);
} catch (err) {
if (err.name !== "TokenExpiredError") return next(err);
// Refresh token
user.generateAccessToken();
// Save and return new user
return user.save();
}
return user;
})
.then((user) => {
// Append user object to the incoming request
req.user = user;
return next();
})
.catch(next);
};
Can anyone help me understand out how I would check both collections (Users & Owners)?

Node.js: Custom header returns undefined when use 'req.header'

I'm fairly new to Express and NodeJS. I'm having trouble accessing my custom created header named auth-token when trying to verify the existing of said user first before allowing them to do any CRUD functionality in the system. It just returned 'undefined' instead of the token I placed in it.
So below is where I created my custom header named auth-token in my home GET router.
// Home GET Router
router.get('/', verifyUser, async (req, res) => {
// get user data by id
const dbData = await All.findById({ _id: req.user._id })
// store token passed as query 'tkn' in 'token' var
const token = req.query.tkn
// create custom header & render 'index.ejs' or homepage
res
.header('auth-token', token)
.render('index', { data: dbData.data })
}
I successfully able to create the custom header auth-token with no problem as shown below in my index or home page:
Right now, I'm trying to save new data inserted by user in the home page by using Home POST Router as shown below. But it will check first whether the user has the token or not using verifyUser1st function:
// Home POST Router
router.post('/', verifyUser1st, async (req, res) => {
// save new data code here...
}
And this is my verifyUser1st function:
function verifyUser1st(req, res, next) {
// get token from header
const token = req.header('auth-token') // this will return undefined
// if have, then allow/continue next(). If don't have, then return error message
if(!token) return res.status(401).json({ message: 'Accessed Denied!' }) // I got this error since token = undefined
try {
// verify the exist token
const varified = jwt.verify(token, process.env.TOKEN_4LOGINUSER)
req.user = varified
next()
} catch(err) {
res.status(400).json({ message: 'Invalid token!' })
}
}
But unfortunately it returns Accessed Denied since the token is undefined.
Should the auth-token be in Request Headers section (in blue circle image above) instead of Response Header section (in red circle image above) in order for it to work?
If yes, then how can I do that? If not, then can you help enlighten me of what things or topics should I learn first in order for me to make this work since I'm kinda new to this HTTP, Express and NodeJS environment?
to answer your question briefly - if you want to pass the auth token in the header then it should be passed in the request header.
However, if you want some middleware to check a token value that you can use later on in the processing chain, then just set it as a custom property on the req object and access it from there. There is no reason to try to jam something into the headers and then parse it out again later.
const auth = (req, res, next) => {
let { token } = req.body;
try {
console.log(token)
if (!token)
return res.status(401).json({ msg: 'no authentication token, authorisation denied' })
const verified = jwt.verify(token, process.env.JWT_KEY);
if (!verified)
return res.status(401).json({ msg: 'no authentication token, authorisation denied' })
req.user = verified.id;
next();
}
catch (err) {
res.status(500).json({ error: err.message });
}
}
So instead of sending the token in a Header we can either grab it from somewhere in the state, or in your case from the local storage.
In the front end we would do something like const token = localStorage.getItem("auth-token");
And then we would pass this token to the API request.

Resources