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.
Related
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);
});
I want to secure my files and not anyone can view my files in express, I want to check who has logged in into my website and then authenticate if he has access to view this file or not , how can I achieve that this is the code I am using to access my files url :
app.use("/profile", express.static(__dirname + '/profile'));
I want only the user that logged in, he is the only one that can view his profile image and if he is not the user then say for example you are not allowed to view this file.
I am using mongodb as a backend but i don't know if its mongodb authentication or express middleware security ?
I Discovered how to solve this :
const checkImg = async (req, res, next) => {
const token = req.cookies.jwt;
if (token) {
jwt.verify(
token,
process.env.JWTKEY,
async (err, decodedToken) => {
if (err) {
res.json({ status: false });
} else {
const user = await UserModal.findById(decodedToken.id);
const imgUrl = req.originalUrl.split('/')[2];
const imgUser = user.profileImg;
if (imgUrl === imgUser || user.admin === "ADMIN") {
next();
}
else {
return res.send("Your are not allowed to view this file")
}
}
}
);
}else{
return res.send("you are not allowed !")
}
}
and here is when i use my middleware :
app.use("/images", checkImg , express.static(__dirname + '/images'));
this code checks if the user is the same user then he can only see the data and also the admin user can display the data , otherwise send him that he is not allowed to get the data
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)?
So this is my code that shows if user session exists and if it exists then it renders user info with the file so I can display the logged in user info there.
app.get('/', async(req, res) => {
if(req.session.user && req.cookies.user_sid){
let user = await User.findOne({username: req.session.user.username});
res.render('index', {user});
} else {
res.render('index');
}
});
But now I want to display user info and in another routes. So in my opinion it would be stupid to repeat again that if statement. What alternative could be there?
It's best to repeat that statement and make the DB call again. In the time between the first request and any subsequent requests, the user may have been logged out (such as by cookies expiring) or user data might have been changed in the database, so it's best to not cache the data and check every time.
Method 1
Add a user details middleware ,which checks if user details is available in session or not and then updates the session object if not available.This way you will avoid redundant calls to db across routes.
app.use(function (req, res, next) {
if(req.session && req.session.userDetails === undefined){
const userDetails = await User.findOne({username: req.session.user.username});
req.session.userDetails = userDetails || undefined;
//optional
req.user = {};
Object.assign(req.user, req.session.userDetails);
}
next()
})
You can pass the userDetails in all your routes with reference to req.user or req.session.userDetails, something like
app.get('/profile', async(req, res) => {
res.render('profile', {user : req.user});
})
Method 2
You can also save the user details in session when the user successfully logs in and use the session reference in all routes, something like
app.post('/authenticate', async (req, res) => {
const userDetails = await User.findOne({ username: req.body.username, password: req.body.password });
if (userDetails.length > 0) {
//when authentication is successsful
req.session.user = userDetails;
}
});
Web app routing novice here. I've got a relatively simple app working based on Node/Express.
The main index of the app is a list of user names, and IDs.
localhost:242
Every user ID is a link to a page with a form to enter additional metadata about that particular user.
localhost:242/user/1398
Everything is working correctly. When I enter some metadata about the user, and submit the form, a POST route is executed, and then I'm redirected back to the original page I was on. Instead of using a redirect, I'd like to be able to re-render that same page, so I can pass some confirmation messages indicating what was just changed.
Here's a simplified version of my code.
// Module imports
var express = require('express');
var validator = require('express-validator');
var router = express.Router();
router.get('/', function(req, res, next) {
db.dataTalk(queryUsers, null, config.connection, function(err, result) {
var listUsers = result;
res.render('index', {
// Index with list of users
title: 'Page Title',
listUsers: listUsers
});
});
});
// GET /user/:id
router.get('/user/:id', function(req, res, next) {
db.dataTalk(queryUserDeets, [req.params.id], config.connection, function(err, result) {
// Details for a single user
var userDetails = result;
res.render('user', {
title: req.params.id,
userDetails: userDetails
});
});
});
// POST /user-update
router.post('/user-update', function(req, res) {
// Here goes a lot of logic to validate the form contents, and update the appropriate databases
// Redirect back to the user page, which should display the updated metadata
res.redirect('/user/' + req.body.userInitID);
});
module.exports = router;
Extract a helper function you can call from both places. Here's one that sticks very close to your original code.
function renderUserPage (userId, res) {
db.dataTalk(queryUserDeets, [userId], config.connection, function(err, result) {
// Details for a single user
var userDetails = result;
res.render('user', {
title: userId,
userDetails: userDetails
});
});
});
// GET /user/:id
router.get('/user/:id', function (req, res) {
renderUserPage(req.params.id, res)
});
// POST /user-update
router.post('/user-update', function(req, res) {
// Here goes a lot of logic to validate the form contents, and update the appropriate databases
// Redirect back to the user page, which should display the updated metadata
renderUserPage(req.body.userInitID, res);
});
Aside: You are ignoring errors from database calls. If you don't at the very least log something for each and every error passed to an async callback, you are going to be blind to problems that would otherwise be straightforward to debug.