Verification of user and admin not working properly in following code: - node.js

In verifyAdmin function the verifyToken is passed where with the help of jwt the user property is added to the request and then using that property's key isAdmin we can check if the user is admin or not but in the below given code it is not working. Also same issue is being faced for the verifyUser.
import jwt from "jsonwebtoken";
import { createError } from "../utils/error.js";
// This verifies that is the token correct i.e. weather person is admin or not
export const verifyToken = (req, res, next) => {
const token = req.cookies.access_token;
if (!token) {
return next(createError(401, "You are not authenticated!"));
}
jwt.verify(token, process.env.JWT, (err, user) => {
if (err) return next(createError(403, "Token is not valid!"));
// Here in place of the req.user you can write any property as nothing is defined in request
req.user = user;
console.log(req.user);
next();
});
};
// To verify the user
export const verifyUser = (req, res, next) => {
// If the user have the token i.e. user needs to be authenticated.
verifyToken(req, res, next, () => {
// If the user id matches or user is admin then CRUD operations can be performed.
if (req.user.id === req.params.id || req.user.isAdmin) {
next();
} else {
return next(createError(403, "You are not authorized!"));
}
});
};
export const verifyAdmin = (req, res, next) => {
verifyToken(req, res, next, () => {
console.log(`Before or After Token`);
if (!req.user.isAdmin) {
next();
} else {
return next(createError(403, "You are not authorized!"));
}
});
};
Here the user details are perfectly verified till verifyToken when passed from verifyAdmin but then it is not checking for the admin or user in verifUser function.

Related

express auth middleware returning "Not authorized" error

I have an auth protect middleware that checks if req.params.id === req.userId(the one returned by bcrypt verify function). I have a protect function which upon bcrypt.verify returns the decoded.id.
The Id returned from req.user._id despite being the same as decoded.id returns "not authorized in the verifyandAuth middleware, however, if I replace req.user._id by decoded.id(in verifyandAuth), the if function works and the middleware goes through without giving the "not authorized error". Can anybody please tell me why that's happening? (req.user._id and decoded.id upon console.log show the same id, as such, there's no mistake there).
Protect Middleware
export const protect = 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, "kris");
req.userId = decoded.id;
req.user = await User.findById(decoded.id).select("-password");
next();
}
} catch (error) {
res.status(400).json(error.message);
}
if (!token) {
return res.status(400).json("Invalid Token");
}
};
auth Middleware
export const verifyandAuth = (req, res, next) => {
protect(req, res, () => {
console.log(req.user._id, req.params.id);
if (req.user._id === req.params.id || req.isAdmin) {
next();
} else {
res.status(400).json("Not authorised");
}
});
};

api call getting affected by another api call's validation

not really sure if my title is correct but my problem is that I have this reset password token checker in my api that seems to get affected by another api that finds a specific user, this api has user validation.
Here is what they look like:
//get specific user
router.get('/:id', validateToken, async (req, res) => {
const id = req.params.id
const user = await User.findByPk(id);
res.json(user);
});
//reset-password token check
router.get('/reset-pass', async (req, res) => {
await User.findOne({
where: {
resetPasswordToken: req.body.resetPasswordToken,
resetPasswordExpires: {
[Op.gt]: Date.now()
}
}
}).then(user => {
if(!user) {
res.status(401).json({ error: 'Password reset link is invalid or has expired.'})
} else {
res.status(200).send({
username: user.username,
message: 'Password reset link Ok!'
});
}
});
});
then here is the validateToken
const validateToken = (req, res, next) => {
const accessToken = req.cookies['access-token'];
if (!accessToken)
return res.status(401).json({error: 'User not authenticated!'});
try {
const validToken = verify(accessToken, JWT_SECRET)
req.user = validToken;
if(validToken) {
req.authenticated = true;
return next();
}
} catch(err) {
res.clearCookie('access-token')
return res.status(400).json({error: err}).redirect('/');
}
};
when I comment out the get specific user api the reset password token check works. If I remove validateToken it returns null instead of giving me the username and message.
One of the things I notice is the route param "/:id", that means that literally everything would be processed by get specific user because all routes start with "/", only use params in routes with a prefix like "/user/:id" that way only the routes that starts with "/user" will execute that code.
Change your code to:
//get specific user
router.get('/user/:id', validateToken, async (req, res) => {
const id = req.params.id
const user = await User.findByPk(id);
res.json(user);
});

Calling next() at the end of custom middleware causes error

I am trying to set up viewpages to show the authenticated user's info such as user's name or email on the page when they are logged in.
To do so, I am using the res.locals function to set the user data at the global level for the pages to access.
const jwt = require("jsonwebtoken")
const User = require("../models/User")
const checkUser = (req, res, next) => {
const token = req.cookies.jwt
if (token) {
jwt.verify(token, "Company_Special_Code", async (err, decodedToken) => {
if (err) {
res.locals.user = null // Set it to null if the user does not exist
next();
} else {
let user = await User.findById(decodedToken.id)
res.locals.user = user
next();
}
})
} else {
res.locals.user = null
next();
}
}
module.exports = {
checkUser
}
The first code, where I call next() function every time the code reaches an endpoint, allows the pages to access the user info without any errors.
However, if I call the next() function only once at the very bottom of the checkUser() function, it causes error claiming that the user is not defined at the view page level. The code is as follows:
const jwt = require("jsonwebtoken")
const User = require("../models/User")
const checkUser = (req, res, next) => {
const token = req.cookies.jwt
if (token) {
jwt.verify(token, "Company_Special_Code", async (err, decodedToken) => {
if (err) {
res.locals.user = null // Set it to null if the user does not exist
} else {
let user = await User.findById(decodedToken.id)
res.locals.user = user
}
})
} else {
res.locals.user = null
}
next();
}
module.exports = {
checkUser
}
If I coded the function correctly, the checkUser() function should get to the next() function at the bottom regardless of the status of jwt token or if there was an error during the token verification process. I would really appreciate your help if you can tell me what I am getting it wrong here...
Your jwt.verify is has an asynchronous callback and next() at the bottom is being called before that returns. So you either need to put next() into that callback, or use jsonwebtoken synchronously. Something like this:
const checkUser = (req, res, next) => {
const token = req.cookies.jwt
if (token) {
try {
const decodedToken = jwt.verify(token, "Company_Special_Code")
// This only runs if the token was decoded successfully
let user = await User.findById(decodedToken.id)
res.locals.user = user
} catch (error) {
res.locals.user = null // Set it to null if the user does not exist
}
} else {
res.locals.user = null
}
next();
}
When you use an async callback like that, javascript will continue processing the rest of the script while that callback is running on the side (more or less). So next() is being called unaware of the need to wait for the callback or anything it might handle.
Your validation part misses the correct error handling in middleware. If token is invalid, then why should user get access to controller, you can send error from middleware itself. If you are not sending error from middleware and calling next(), then will defeat purpose of your authentication middleware.
Update your code as follows,
const jwt = require("jsonwebtoken")
const User = require("../models/User")
// The routes, which does not requires aurthentication
const usecuredRoutes = []
const checkUser = (req, res, next) => {
if(usecuredRoutes.indexOf(req.path) === -1){
const token = req.cookies.jwt
if (token) {
jwt.verify(token, "Company_Special_Code", async (err, decodedToken) => {
if (err) {
res.locals.user = null // Set it to null if the user does not exist
// Token is invalid, then telll user that he is don't have access to the resource
res.status(403).send('Unauthorized')
} else {
let user = await User.findById(decodedToken.id)
res.locals.user = user
next();
}
})
} else {
// Token does not exists, then telll user that he is don't have access to the resource
res.status(403).send('Unauthorized')
}
} else {
next()
}
}
module.exports = {
checkUser
}

What is the best way to verify authentication token of different type of users?

Let's say I have 10 routes and each route is accessible to only a specific type of user. When a user logins, a token is generated.These user tokens are generated with their _id and a token secret which is stored in .env file.
Normally token verification for each user type is done with separate functions because different type of user has different token secret. For example, user1's token secret maybe TOKEN_SECRET_USER2 = 6ygfewf6hj, and user2's token maybe TOKEN_SECRET_USER1 = 87uhjkaf89.
And when any request is made to a route, the user token is verified to see if the user can access that route or not.
Here's two example route accessible to different user types,
// Route accessible to user type 1
router.get("/foo", verifyTokenUSER1, async (req, res) => {
// All the good stuff
});
// Route accessible to user type 2
router.post("/bar", verifyTokenUSER2, async (req, res) => {
// All the good stuff
});
Here's some method of verification module looks like,
// Verification for user 1
const verifyTokenUSER1 = (req, res, next) => {
const token = req.header("auth-token");
if (!token) return res.status(401).send();
try {
jwt.verify(token, process.env.TOKEN_SECRET_USER1);
next();
} catch (err) {
res.status(401).send();
}
};
// Verification for user 2
const verifyTokenUSER2 = (req, res, next) => {
const token = req.header("auth-token");
if (!token) return res.status(401).send();
try {
jwt.verify(token, process.env.TOKEN_SECRET_USER2);
next();
} catch (err) {
res.status(401).send();
}
};
As you can see, there's only one change in the above methods, which is the access token secret of the user types.
I would like to verify them using 1(one) single function if possible. But I can't pass any value as parameter to the verify methods. So, how can I remove duplication here?
You actually can pass a parameter to the verify method if you use bind (mdn):
// Route accessible to user type 1
router.get("/foo", verifyToken.bind(null, process.env.TOKEN_SECRET_USER1), async (req, res) => {
// All the good stuff
});
// Route accessible to user type 2
router.post("/bar", verifyToken.bind(null, process.env.TOKEN_SECRET_USER2), async (req, res) => {
// All the good stuff
});
the bind method of a function here is receiving 2 parameters: first, the this context for the function, and second, the first argument. It returns a new function that will receive the provided token as the first argument, and will receive req, res, next as the next arguments.
const verifyToken = (tokenSecret, req, res, next) => {
const token = req.header("auth-token");
if (!token) return res.status(401).send();
try {
jwt.verify(token, tokenSecret);
next();
} catch (err) {
res.status(401).send();
}
};
Another way to do this that is equivalent is building a verification method "factory" which returns a verification function that has a closure (mdn) on the token:
// Route accessible to user type 1
router.get("/foo", getTokenVerifier(process.env.TOKEN_SECRET_USER1), async (req, res) => {
// All the good stuff
});
// Route accessible to user type 2
router.post("/bar", getTokenVerifier(process.env.TOKEN_SECRET_USER2), async (req, res) => {
// All the good stuff
});
// this function returns a new function, with a closure on the provided tokenSecret
const getTokenVerifier = (tokenSecret) => {
return (req, res, next) => {
const token = req.header("auth-token");
if (!token) return res.status(401).send();
try {
jwt.verify(token, tokenSecret);
next();
} catch (err) {
res.status(401).send();
}
}
}

Route available with or without token JWT+PASSPORT

I want that I can access a router with or without a token. If user has a token, than give me req.user
Like this:
router.get('/profile', function(req, res) {
if(req.user) { // or if (req.isAuthenticated())
res.send('logged')
} else {
res.send('not loggedIn')
}
});
My app:
var JwtStrategy = require('passport-jwt').Strategy,
ExtractJwt = require('passport-jwt').ExtractJwt;
var opts = {}
opts.jwtFromRequest = ExtractJwt.fromAuthHeader();
opts.secretOrKey = 'sh';
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
User.findOne({id: jwt_payload.sub}, function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
done(null, user);
} else {
done(null, false);
// or you could create a new account
}
});
}));
If I try to access /profile without a token, works fine.
But, when a try to access /profile with a token in header give me not loggedIn
I want to access it even without a token and, if I provided a toke, give me the user.
ps: Already tested using passport.authenticate('jwt') in route and works. If I give token let me access, if not give me unauthorized.
This is how I'm doing it:
const passport = require('passport');
const optionalJwt = function (req, res, next) {
if (req.headers['authorization']) {
return passport.authenticate('jwt', { session: false })(req, res, next);
}
return next();
};
app.get('/my/route', optionalJwt, function (req, res, next) {
if (req.isAuthenticated()) {
res.send('My name is ' + req.user.name);
} else {
res.send('I\'m not authenticated :(');
}
});
It will still fail if the auth header is included but the token has expired or it's invalid, but it might serve the purpose. For me it did.
Change you router as follows
router.get('/profile', authenticateUser(), profile());
function authenticateUser(req, res, next) {
// your strategy here...
if (authenticated) {
req.user = user;
next();
} else {
return res.status(401).send("Not authenticated");
}
}
function profile(req, res, next) {
var userId = req.user.id;
User.findById(userId, function(err, user) {
if (err) { return res.status(500).json(err); }
return res.json(user);
})
}
you should be using one of the below to access request data
if (req.params.user) {do something...}
or
if (req.body.user) {do something...}
the request object has no user attribute.

Resources