So I am creating a social media application.
I used JWT token for verification on all endpoints. It's giving me custom error of "You are not authorized, Error 401"
For example: Create post is not working:
This is my code for JWT
const jwt = require("jsonwebtoken")
const { createError } = require ("../utils/error.js")
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_SECRET, (err,user) => {
if(err) return next(createError(401,"Token is not valid!"))
req.user = user
next()
}
)
}
const verifyUser = (req, res, next) => {
verifyToken(req,res, () => {
if(req.user.id === req.params.id || req.user.isAdmin) {
next()
} else {
return next(createError(402,"You are not authorized!"))
}
})
}
const verifyAdmin = (req, res, next) => {
verifyToken(req, res, next, () => {
if (req.user.isAdmin) {
next();
} else {
return next(createError(403, "You are not authorized!"));
}
});
};
module.exports = {verifyToken, verifyUser, verifyAdmin}
This is my createPost API:
const createPost = async (req, res) => {
const newPost = new Post(req.body);
try {
const savedPost = await newPost.save();
res.status(200).json(savedPost);
} catch (err) {
res.status(500).json(err);
}
}
Now, in my routes files, I have attached these functions with every endpoints.
For example: In my post.js (route file)
//create a post
router.post("/", verifyUser, createPost);
When I try to access it, this is the result
But, when I remove this verify User function from my route file, it works okay.
I have tried to re-login (to generate new cookie) and then try to do this but its still giving me error.
What can be the reason?
P.S: my api/index.js file https://codepaste.xyz/posts/JNhIr9W6zNnN26CH9xWT
After debugging, I found out that req.params.id is undefined in posts routes.
It seems to work for user endpoints since it contains req.params.id
const verifyUser = (req, res, next) => {
verifyToken(req,res, () => {
if(req.user.id === req.params.id || req.user.isAdmin) {
next()
} else {
return next(createError(402,"You are not authorized!"))
}
})
}
So I just replaced === with || and its working. (but its not right)
if(req.user.id || req.params.id || req.user.isAdmin) {
Can anyone tell me the how can I truly apply validation here since in my posts routes i dont have user id in params
Related
I am getting the following error on my node api, which is really just console logging the request at this point.
router.get('/booksByISBN', checkRole, async (req, res) => {
console.log(req.params)
return res.sendStatus(200);
});
node:internal/errors:484
ErrorCaptureStackTrace(err);
^
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
I believe the issue is because of pre-flight CORS data, but no clue how to fix it.
There is one API call in my Angular 15 application, but upon inspection of the Network tab, I see two api calls are actually being made to my endpoint.
I understand this is because of the CORS options request, but I don't know how to fix it to let the API go through.
CheckRole function
var checkRole = async function CheckRoleAuth (req, res, next) {
try {
const token = req.headers.authorization.split(' ')[1];
const decodedToken = jwt.verify(token, envs.jwtSecret);
await User.findById(decodedToken.userId)
.then(foundUser => {
if (foundUser) {
if (foundUser.role != null || foundUser.role != '') {
if (foundUser.role.includes('Admin'))
{
req.userData = {
email: decodedToken.email,
id: decodedToken.id
};
next();
} else {
return res.sendStatus(401);
}
} else {
return res.sendStatus(401);
}
}
})
.catch(err => {
return res.sendStatus(401);
});
} catch (error) {
return res.sendStatus(401);
}
}
You are combining async/await with then/catch in your checkRole middlware, so probably both your checkRole middleware and your endpoint handler try to send back the response.
Refactor your checkRole middleware like this:
const checkRole = async function CheckRoleAuth(req, res, next) {
try {
const token = req.headers.authorization.split(' ')[1];
const decodedToken = jwt.verify(token, envs.jwtSecret);
const user = await User.findById(decodedToken.userId).lean();
if (!user) return res.sendStatus(401);
if (!user?.role?.includes('Admin')) return res.sendStatus(403);
req.userData = { email: decodedToken.email, id: decodedToken.id };
next();
} catch (error) {
return res.sendStatus(401);
}
};
I have this piece of code that works fine on postman when I set the header to the token generated from the access token. but when I'm using the same piece of code on the browser when I log in, I get the authorization access, but the moment I manually input the new router, I get an unauthorized error.
const verifyToken = (req, res, next) => {
const authHeader = req.headers["authorization"];
if (authHeader) {
const token = authHeader.split(" ")[1];
jwt.verify(token, process.env.jwtToken, (err, user) => {
if (err) {
res.status(500).json("Invalid Token");
} else {
req.user = user;
next();
}
});
} else {
res.status(500).json("You are not authenticated");
}
};
const verifyAndAuth = (req, res, next) => {
verifyToken(req, res, () => {
if (req.params.id === req.user.id) {
next();
} else {
res.status(500).json("You are not verified");
}
});
};
the verifyAndAuth middleware is added to every route for example:
router.get("/", admin, async (req, res) => {
try {
const noteData = await users.find();
res.status(200).json(noteData)
} catch (err) {
res.status(500).json(err)
}
});
the JWT verification function accepts the req, res and next as its params. I need to pass an additional string 'Admin' so that only admin users may access this API
My jwtVerification.js code:
module.exports = async function (req, res, next) { //I need to be able to add role to this call
try {
const token = req.header("Authorization");
if (!token) return res.status(401).send('Invalid access token.');
const _token = token.substring(7, token.length);
const decoded = jwt.verify(_token, process.env.JWT_PRIVATE_KEY)
const user = await prisma.user.findFirst({ where: { id: decoded.id } });
if (!user) return res.status(401).send('Invalid access token.');
//I need to be able to read the role so that I can do the following verifications
//if(!role) next();
//else{
// if(user.role !== role || decode.role !== role) return res.status(403).send('Forbidden!')
// else next();
//}
next();
} catch (error) {
res.status(401).send(error.message);
}
};
finally, the API call itself:
//use verifyJWT('Admin') for example
router.post('/test', verifyJWT, async (req, res) => {
res.send('hi');
})
You cat use some thing like this:
module.exports = function (myParam) => {
return async function (req, res, next) {
//use myParam here
try {
const token = req.header("Authorization");
if (!token) return res.status(401).send('Invalid access token.');
const _token = token.substring(7, token.length);
const decoded = jwt.verify(_token, process.env.JWT_PRIVATE_KEY)
const user = await prisma.user.findFirst({ where: { id: decoded.id } });
if (!user) return res.status(401).send('Invalid access token.');
//I need to be able to read the role so that I can do the following verifications
//if(!role) next();
//else{
// if(user.role !== role || decode.role !== role) return res.status(403).send('Forbidden!')
//}
next();
} catch (error) {
res.status(401).send(error.message);
}
}
};
And after that use the middleware this way:
router.post('/test', verifyJWT(someParam), async (req, res) => {
res.send('hi');
})
I have an app where I have public routes and authenticated routes. isAuthenticated were applied for example to a news controller.
globalRouter: function (app) {
app.use((req, res, next) => {
logger.log("Endpoint: ", req.originalUrl);
next();
});
const userRouter = require("./user/controller");
const globalRouter = require("./global/controller");
const newsRouter = require("./news/controller");
app.use("/user", userRouter);
app.use("/global", globalRouter);
app.use("/news", middleware.isAuthenticated(), newsRouter); // here
}
And here is the isAuthenticated code written in middleware.js file.
const security = require("../utils/security");
const service = require("../user/service");
exports.isAuthenticated = function (req, res, next) {
let authorization = req.headers.authorization;
let token = null;
if (authorization.startsWith("Bearer ")) {
token = authorization.substring(7, authorization.length);
if (token !== null) {
service.checkUserTokenMiddleware(token, security).then((response) => {
console.log("checkUserTokenMiddleware", response);
if (response) {
next();
}
});
}
}
};
The problem is that I'm getting this error below when I npm start the app
TypeError: Cannot read property 'headers' of undefined at Object.exports.isAuthenticated
What am I missing here?
why do I get such an error meanwhile in my other file using the same method like req.body.blabla or req.headers.blabla is fine?
Any help will be much appreciated.
Regards.
Simply remove the brackets after the function call:
app.use("/news", middleware.isAuthenticated, newsRouter);
You don't have to call the function in the callback to app.use, Express will itself pass in req,res,next to the auth function and call it.
It depends on how you import, middleware.js. Since you are exporting, isAuthenticated as function. This should be not called before passing to app.use.
Other things to be noticed, you never call the next function on error or else.
Please have a look in the below example.
// middleware.js
const security = require("../utils/security");
const service = require("../user/service");
exports.isAuthenticated = function (req, res, next) {
let authorization = req.headers.authorization;
let token = null;
if (authorization.startsWith("Bearer ")) {
token = authorization.substring(7, authorization.length);
if (token !== null) {
service
.checkUserTokenMiddleware(token, security)
.then((response) => {
if (response) {
next();
}
})
.catch((error) => next(error));
} else {
next("UNAUTHORIZED");
}
} else {
next("UNAUTHORIZED");
}
};
// app.js
const middleware = require("./middleware")
app.use((req, res, next) => {
logger.log("Endpoint: ", req.originalUrl);
next();
});
const userRouter = require("./user/controller");
const globalRouter = require("./global/controller");
const newsRouter = require("./news/controller");
app.use("/user", userRouter);
app.use("/global", globalRouter);
app.use("/news", middleware.isAuthenticated, newsRouter); // here
So I'm creating an authentication route but failing after executing the middleware.
verifyToken.js
module.exports = function (req, res, next) {
const token = req.get('auth-token')
if (!token) return res.status(401).send('Access Denied!')
try {
const verified = jwt.verify(token, process.env.TOKEN_SECRET)
req.user = verified
console.log(req.user) // successfully logging
next()
} catch (err) {
res.setHeader('Content-Type', 'application/json');
res.status(403).send('Invalid Token')
}
}
user.controller.js
exports.currentUser = verifyToken, async (req, res) => { // after verify token throwing an error 404
console.log('HIT') // not logging
// return res.send(req.user)
}
user.route.js
const { currentUser } = require('../controllers/users');
router
.route('/currentuser')
.post(currentUser)
I tried your code and I couldn't log 'HIT' as well. I suggest the following, split the exports # exports.currentUser into
var verifyToken = require('./verifyToken.js')
var response = async (req, res) => {
console.log('HIT') // not logging
// return res.send(req.user)
}
module.exports.currentUser = {verifyToken, response}
Then re-write route.js like this to get it to work.
const { currentUser } = require('./controller.js');
router.get('/currentUser', currentUser.verifyToken, currentUser.response)
To utilize next(), I had to use router.get('/get', middleware, callback). I changed the codes so that I could test it. You will need to edit the codes according to your context!