How to call a middleware function from inside a middleware function? - node.js

Noob question here.
I am using Node.JS and Express (with JWT Auth) and am really struggling with the middleware.
Sometimes I need to know if a user is logged in, but don't need to force them to be logged in (such as authorize middleware). For this, I create a new middleware isLoggedIn. The problem here is that if I find the user is logged in, I then want to authorize them so I can use the req.auth property. I know this is not the most resource-efficient method, but was the best I could think of. Even now it doesn't work, It just skips over the auth part. I have mainly been debugging with console.log(); and still can't find the problem.
function isLoggedIn() {
return (req, res, next) => {
var clientToken
// Check if the user has token. If not return null.
if (req.headers.authorization && req.headers.authorization.split(" ")[0] === "Bearer") {
clientToken = req.headers.authorization.split(" ")[1];
} else if (req.query && req.query.token) {
clientToken = req.query.token;
} else if (req.cookies && req.cookies['session']) {
clientToken = req.cookies['session'];
} else {
clientToken = null;
}
if (!clientToken) next();
else authorize();
}
}
function authorize(roles = []) {
console.log("1");
// roles param can be a single role string (e.g. Role.User or 'User')
// or an array of roles (e.g. [Role.Admin, Role.User] or ['Admin', 'User'])
if (typeof roles === 'string') {
roles = [roles];
}
console.log("2");
return [
//console.log("3"),
// authenticate JWT token and attach user to request object (req.auth)
jwt({
secret,
algorithms: ['HS256'],
getToken: function fromHeaderOrQuerystring(req) {
console.log("4");
if (req.headers.authorization && req.headers.authorization.split(" ")[0] === "Bearer") {
console.log("why is it here?");
return req.headers.authorization.split(" ")[1];
} else if (req.query && req.query.token) {
console.log("query string?")
return req.query.token;
} else if (req.cookies && req.cookies['session']) {
console.log("5");
return req.cookies['session'];
}
console.log("null?");
return null;
}
}),
//console.log("6"),
// authorize based on user role
async(req, res, next) => {
//this is the part that doesn't run... I think...
console.log("7");
const account = await User.findOne({ id: req.auth.sub });
const refreshTokens = await refreshToken.find({ account: account.id });
if (!account || (roles.length && !roles.includes(account.role))) {
// account no longer exists or role not authorized
return res.status(401).json({ message: 'Unauthorized' });
}
// authentication and authorization successful
req.auth = account;
//req.auth.role = account.role;
req.auth.ownsToken = token => !!refreshTokens.find(x => x.token === token);
next();
}
];
}
As you can see: for example. app.get("/", isLoggedIn(), (req, res) => res.render('index')); Here I am using isLoggedIn, because they don't need to be logged to see index, but I want to be able to use req.auth if I can (they are logged in).
Compared to here: when I need to use authorize, app.get("/user", authorize(), (req, res) => res.render('user')); They cannot access the user route if they aren't logged in, that doesn't make sense.
TL;DR, I am a big noob. I have probably made an extremely obvious mistake and don't know what to google to find a solution. I have been experimenting with different stuff and console.log() and still can't find a solution.
Thank you everyone, for your help!
THE SOLUTION:
So, the solution? Change your approach. My approach to this was completely wrong, however, #HeikoTheißen was able to show me the right way. I only had to make a few small tweaks to his provided answer.
Unprotected Route:
app.get("/unprotected", authorize.authorize(), authorize.NoLoginRequired, (req, res) => res.render('unprotectedview'));
Protected Route:
app.get("/user", authorize.authorize(), (req, res) => res.render('user'));
Authorize():
Pretty much stayed the same. I did note, however, that it should be reformed to follow middleware like express documentation. rather than returning an array of functions to run.
isLoggedIn(): [REMOVED]
NoLoginRequired:
function NoLoginRequired(err, req, res, next) { //<-- make sure follow the (err, req, res, next) and do not add ().
if (err && err.name === "UnauthorizedError") { //<-- Needed to add this (err) as this was being triggered without an err being defined. (not sure how though, just saw in console)
next(); // proceed without logged-in user (works as expected. thanks!)
} else {
next(err); // report all other errors
}
}
I really appreciate your help solving this issue and hope to reform it to become clearer to the reader. I hope this may be able to help others with a similar issue. (although it's probably just because I am a noob)

You try to find out whether a user is logged in and then validate their login credentials, which leads to much duplicated code. Instead, you should always try to authorize() the user and for certain views that don't require a logged-in user catch the UnauthorizedError that express-jwt might throw with an error-handling middleware:
function NoLoginRequired(err, req, res, next) {
if (err.name === "UnauthorizedError") {
req.auth.name = "not logged-in user";
next(); // proceed without logged-in user
} else
next(err); // report all other errors
}
app.get("/protectedview", authorize(), function(req, res, next) {...});
app.get("/unprotectedview", authorize(), NoLoginRequired, function(req, res, next) {
res.end("Hi " + req.auth.name);
});

Related

Authorization middleware JWT confusion

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
}

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)?

jwt token validation on main page

before i started working with reactJS i was using express sessions (with expressJS of course) to determine whether user was authenticated or not, my middleware was passed in /profile URL like this router.use('/profile', middleware, require('./profilePageFile')) and if user was not authenticated i was redirecting to login page with simple code
if(!req.session.user){
res.redirect('/login')
}
i tried to use redirecting with react too but since react has it's own routing system (react-router-dom) and express is only needed for creating APIs when i was logging in /profile url it was still showing me page content and redirecting me after xxx milliseconds later, and i think it would be better practice if i have my profile page and main page on default url ( 'domain.com/' ), as i see many websites are using this technique including Facebook, at this point i was trying to make something like this: if user has not token or token expired, don't display some "hello user" button, otherwise display it. my only problem is that i do not know how to do that.
if i have boolean in my react state called isAuthenticated or something like this which determines whether user is authenticated or not according to the header that i send from server-side, it would be bad practice for security, i think, and also when i tried that, it did not work anyway. at this point only thing that i can do is to pass req.userId to client if token exists. this works but it is not enough, if anyone got the point i will be glad if i get help
here is my middleware code
const guard = (req, res, next) => {
const token =
req.body.token ||
req.query.token ||
req.headers["x-access-token"] ||
req.cookies.token;
if (!token) {
res.status(401).send({ auth: false });
} else {
jwt.verify(token, process.env.SECRET, function(err, decoded) {
if (err) {
return res.status(500).send({
message: err.message
});
}
req.userId = decoded.id;
res.status(200).send({ auth: true });
next();
});
}
};
I have made two changes to your code.
const guard = (req, res, next) => {
const token = req.body.token ||
req.query.token ||
req.headers['x-access-token'] ||
req.cookies.token;
if (!token) {
// Authentication failed: Token missing
return res.status(401).send({ auth: false })
}
jwt.verify(token, process.env.SECRET, function (err, decoded) {
if (err) {
// Authentication failed: Token invalid
return res.status(401).send({
auth: false,
message: err.message
})
}
req.userId = decoded.id
next()
})
}
First, inside the if(err) condition I have changed the status code to 401 because if the token is invalid, it will raise the error here.
Secondly, I have removed the res.status(200).send({auth:true}) from the bottom of the function.
This is because the middleware should pass on to the route (which we are trying to protect with the JWT check) to respond. This was responding to the request before it got to the actual route.

sails session writing bug

I'm using sails 0.10.4 and stumbled with one pretty annoying bug. When user logs in I write his data into the req.session.user then in policies I can retrieve his data such as his role, password etc. But the req.session.user becomes undefined when I go out of the login action. Do you have any ideas how to handle this? Here's the code:
api/controllers/User.js :
module.exports = {
login: function (req, res) {
Users.findOneByEmail(req.param('email'))
.exec(function (err, user) {
if ((err) || (!user)) {
res.send({
error: 'User not found'
});
return;
}
if (!passwordHash.verify(req.param('password'), user.password)) {
res.send({
error: 'Incorrect passwpord'
});
return;
}
req.session.user = user;//I write user into the session
res.send({
user: user
});
});
}
}
api/policies/isLoggedIn.js
module.exports = function (req, res, next) {
if (req.headers.authentication) {
var credentials = JSON.parse(req.headers.authentication);
if(req.session.user.login === credentials.login)//User doesn't exist in session
return next();
}
}
In a testing environment , this issue can happen when testing with Supertest and not defining an agent
var agent = request.agent(app);
agent.post('/api/login',{email:'foo#bar.com',password:'foobar})
.end(function(err,res){...; done();});
It is the correct way to work with sessions, simply using request.post would not work as it would reinit the session variable as soon as the response is sent, even if we are chaining requests inside the same test.
Learnt it the hard way, so I hope it can help some lost developper.

Verifying headers at top-level

I have a Node.js app built with Express.js framework.
I want to check that the user is authorized to do a certain request, I do this by requiring the clients to supply an access token in a header.
I don't want to add this to each of the individual functions that the clients have access to. Like this, for an info request about a user:
exports.info = function(req, res) {
var userId = req.params.id,
accessToken = req.headers["accesstoken"];
console.log("received request to get info for userID <"+ userId +">");
users.User.findOne({accessToken: accessToken}, function(err, user) {
if(user == null) {
...
How can I do this at a higher level? Can I set this header requirement somewhere on a global for express?
I want to do this basically for all functions except for the user login function, so all functions except for one.
You can make a small middleware:
verifyUser = function(req,res,next){
var userId = req.params.id, accessToken = req.headers["accesstoken"];
console.log("received request to get info for userID <"+ userId +">");
users.User.findOne({accessToken: accessToken}, function(err, user) {
if(user == null) {
...
}
next()
}
}
Then:
On one request:
app.get("/user/info", verifyUser, exports.info)
On a selection of requests:
app.all(SomeRegex, verifyUser)
On all resquests:
app.use(verifyUser)
You can create a middleware and set it up on each route, you need to authorize. Example:
var myAuthMiddleware = function (req, res, next) {
// Here goes your code to check if the user complies
// with the conditions. You can use req.headers, req.user, etc
if (conditionIsMet) return next(); // If the user complies, you continue the process
// The user doesn't comply
return res.send('Error');
}
Then, you use his middleware in the needed routes:
app.get('/my-route', myAuthMiddleware, myRouteHandler);
app.post('/another-route', myAuthMiddleware, myOtherRouteHandler);
// This one doesn't need auth
app.get('/', indexHandler);
Just add your function as one more of the express middleware that runs before all your request processing.
app.use(function(req, res, next) {
var userId = req.params.id,
accessToken = req.headers["accesstoken"];
console.log("received request to get info for userID <"+ userId +">");
users.User.findOne({accessToken: accessToken}, function(err, user) {
if(user != null) {
return next(); // This is ok, keep processing
} else {
// don't call next, redirect to login page, etc...
}
}
app.get('/home', ...);
apg.get('/some_other_page');
You call next to get express to process as usual, or you use redirect, or return an error and don't call next.

Resources