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)
CodePudding user response:
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);
});