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!
Related
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
exports.checkTokenMW = async (req, res, next) => {
try{
const token = req.cookies["access"]
const authData = await this.verifyAccessToken(token);
console.log(authData, "checkingTokenmw")
res.json(authData)
}catch(err){
res.status(401);
}
next()
};
exports.verifyAccessToken = async (accessToken) => {
return new Promise((resolve, reject) => {
jwt.verify(accessToken, keys.accessTokenSecret, (err, authData) => {
if(err){
console.log(err)
return reject(createError.Unauthorized());
}
console.log(authData, "accesstoken")
return resolve(authData);
});
})
};
exports.verifyRefreshToken = async (refreshToken, res) => {
return new Promise((resolve, reject) =>{
//const refreshToken = req.body.refreshToken;
jwt.verify(refreshToken, keys.refreshTokenSecret, (err, authData) =>{
if (err) { reject(createError.Unauthorized()); return }
const userId = authData.userId
return resolve(userId)
})
})
};
exports.logOut = async (req, res) => {
try{
console.log("----------- logout")
const refreshToken = req.cookies["refresh"];
if(!refreshToken) {throw createError.BadRequest();}
const user = await this.verifyRefreshToken(refreshToken);
res.clearCookie("refresh")
res.clearCookie("access")
res.sendStatus(204);
res.redirect("/");
return res.send()
}catch(err){
console.log(err)
}
};
**The routes file has code something like this **
app.get('/api/logout', authService.checkTokenMW,authService.logOut)
I have been trying to tackle this error from a while not exactly sure what header is setting itself multiple times
**Here is the error **
**
> Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
> at ServerResponse.setHeader (_http_outgoing.js:561:11)
> at ServerResponse.header (E:\COURSE-WEBSITE\main-backend\wiz_backend\node_modules\express\lib\response.js:771:10)
> at ServerResponse.append (E:\COURSE-WEBSITE\main-backend\wiz_backend\node_modules\express\lib\response.js:732:15)
> at ServerResponse.res.cookie (E:\COURSE-WEBSITE\main-backend\wiz_backend\node_modules\express\lib\response.js:857:8)
> at ServerResponse.clearCookie (E:\COURSE-WEBSITE\main-backend\wiz_backend\node_modules\express\lib\response.js:804:15)
> at exports.logOut (E:\COURSE-WEBSITE\main-backend\wiz_backend\controllers\auth-controller.js:131:13)
> at processTicksAndRejections (internal/process/task_queues.js:95:5) {
> code: 'ERR_HTTP_HEADERS_SENT'
> }
**
The problem is inside the middleware function. Once you check for authData you don't need to send it back to response using res.json(authData). Because after that response will be sent and your next() function will be triggered anyways. Since next() will be called your route handler will try to also send another response which is the conflict you face. At the same time, in catch block, you need to have a return statement, so the function execution will be stopped, and it will not reach till next()
exports.checkTokenMW = async (req, res, next) => {
try{
const token = req.cookies["access"]
const authData = await this.verifyAccessToken(token);
console.log(authData, "checkingTokenmw")
// res.json(authData) // <- remove this line
}catch(err){
return res.status(401); // <- here
}
next()
};
The specific error you report is caused when your code tries to send more than one response to a given incoming request. You get to send one and only one response per request. So, all code paths, including error code paths have to be very careful to make sure that you are sending exactly one response.
Plus, if you have already sent a response, do not call next() because that will continue routing to other requests and eventually attempt to send some response (a 404 if no other handlers match).
With these in mind, you have several places your code needs fixing.
In your checkTokenWM:
exports.checkTokenMW = async (req, res, next) => {
try{
const token = req.cookies["access"]
const authData = await this.verifyAccessToken(token);
console.log(authData, "checkingTokenmw")
res.json(authData)
}catch(err){
res.status(401);
}
next()
};
You are calling res.json(authData) and then also calling next(). But, if the purpose of this is to be middleware that continues routing, then you should not be sending any response here if the token passes.
Then, in the catch block, you're setting a status, but not sending a response - that's not technically wrong, but probably not what you want. I'd suggest fixing both of those like this:
exports.checkTokenMW = async (req, res, next) => {
try{
const token = req.cookies["access"]
const authData = await this.verifyAccessToken(token);
console.log(authData, "checkingTokenmw");
// the access token verified so continue routing
next();
}catch(err){
// token did not verify, send error response
res.sendStatus(401);
}
};
In your logout:
exports.logOut = async (req, res) => {
try{
console.log("----------- logout")
const refreshToken = req.cookies["refresh"];
if(!refreshToken) {throw createError.BadRequest();}
const user = await this.verifyRefreshToken(refreshToken);
res.clearCookie("refresh")
res.clearCookie("access")
res.sendStatus(204);
res.redirect("/");
return res.send()
}catch(err){
console.log(err)
}
};
You are attempting to send three responses upon successful logout and no responses upon error.
First off, res.sendStatus(204) sets the status AND sends an empty response. res.status(204) would just set the status for a future call that actually sends the response if that's what you meant to do. But, with that status, you can't then do res.redirect().
It's not entirely clear what you're trying to do here. If you want to redirect, then that needs to be a 3xx status so you can't use 204. I'm going to assume you want to do a redirect.
I'd suggest fixing by changing to this:
exports.logOut = async (req, res) => {
try{
console.log("----------- logout")
const refreshToken = req.cookies["refresh"];
if(!refreshToken) {throw createError.BadRequest();}
const user = await this.verifyRefreshToken(refreshToken);
res.clearCookie("refresh");
res.clearCookie("access");
res.redirect("/");
}catch(err){
// can't call logout, if you weren't logged in
res.sendStatus(401);
}
};
FYI, most apps won't make it an error to call logout if you weren't logged in. They would just clear the cookies and redirect to home either way as it's really no issue whether they were previously logged in or not. The problem with doing it your way is if the cookies somehow get corrupted, then your code doesn't let the user attempt to clear things up by logging out and logging back in.
So, I'd probably just skip the token check entirely:
exports.logOut = async (req, res) => {
res.clearCookie("refresh");
res.clearCookie("access");
res.redirect("/");
};
And, also I'd change this:
app.get('/api/logout', authService.checkTokenMW,authService.logOut)
to this:
app.get('/api/logout', authService.logOut);
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
I'm authenticating calls to my express API using passport. I have a pretty standard setup:
/* Passport Setup */
const jwtOptions = {
jwtFromRequest: ExtractJwt.fromAuthHeaderWithScheme('Bearer'),
secretOrKey: config.auth.passport.key,
}
passport.use(
'jwt',
new JWT.Strategy(jwtOptions, (payload, done) => {
console.log('Using JWT Strategy')
User.findOne({ email: payload.email }, (err, user) => {
if (err) {
return done(err, false)
}
if (user) {
done(null, user)
} else {
done(null, false)
}
})
}),
)
/* Middleware */
const checkToken = passport.authenticate('jwt', { session: false })
const logAuthInfo = (req, res, next) => {
console.log(req.headers)
console.log(req.user)
}
/* Routes */
app.use(passport.initialize())
app.use('/graphql', checkToken, logAuthInfo, graphqlHTTP(graphQLConfig))
// other REST routes, including login
My login route returns a JWT, and when a request is made to /graphql with this token, everything works. But, an unauthenticated request (with no token) returns a 401. What I'd like to do differently is use the checkToken middleware on all requests, assigning req.user to either the authenticated user data or false. I'd then handle any authorization elsewhere.
When I make a request without a token, I don't see 'Using JWT Strategy' log to the console, so that middleware isn't even running.
Any ideas?
Ok, I figured this out shortly after posting this. For anyone coming here with the same question -- the solution is not using passport-jwt to achieve this, but rather the underlying jsonwebtoken.
My working middleware now looks like:
const jwt = require('jsonwebtoken')
const PassportJwt = require('passport-jwt')
const getUserFromToken = (req, res, next) => {
const token = PassportJwt.ExtractJwt.fromAuthHeaderWithScheme('Bearer')(req)
jwt.verify(token, jwtSecret, (err, decoded) => {
if (err) {
req.user = false
next()
return
}
req.user = decoded
next()
})
}
app.use(getUserFromToken)
app.use('/graphql', graphqlHTTP(graphQLConfig))
// Elsewhere, in my GraphQL resolvers
const userQuery = (obj, args, request, info) => {
// ^^^^^^^
// I've also seen this parameter referred to as 'context'
console.log(request.user) // either 'false' or the serialized user data
if (req.user) {
// do things that this user is allowed to do...
} else {
// user is not logged in, do some limited things..
}
}
i tried everything to store the token. nothing works.
things i tried: Cookies, headers, localStorage.
basically nothing is defined in authenticate.js so i get a 401.
what am i doing wrong ?
in app.js
{authenticate} = require('./public/middleware/authenticate');
// POST /users/login {email, password}
function userLogin(req, res){
var body = _.pick(req.body, ["email", "password"]);
User.findByCredentials(body.email, body.password).then(function(user){
return user.generateAuthToken().then(function(token){
// res.cookie('authorization', token).send(user);
res.setHeader('set-cookie',token).send(user);
// localStorage.setItem('token',token');
// res.header('x-auth',token).send(user);
console.log('APP.JS', token);
});
}).catch(function(e){
res.status(400).send();
});
}
app.post('/users/login', userLogin);
in Authenticate.js
const {User} = require('.././models/users-Model');
const cookieParser = require('cookie-parser');
const express = require('express');
const requestCookies = require('request-cookies');
var app = express();
app.use(cookieParser());
var authenticate = function(req, res, next){
// var token = req.cookie.authorization;
// var token = req.header('x-auth');
var token = req.header('set-cookie');
// var token = Cookie('auth');
// var token = localStorage.getItem("token");
console.log('authenticate.js',token);
User.findByToken(token).then(function(user){
if(!user){
return Promise.reject();
}
req.user = user;
req.token = token;
next();
}).catch(function(e){
res.status(401).send();
});
}
module.exports = {authenticate};
in app.js
function listPost(req, res){
var Data = new budgetCalculator({
_creator:req.user._id,
_id: req.body._id,
firstItem: req.body.firstItem,
firstPrice: req.body.firstPrice,
secondItem: req.body.secondItem,
secondPrice: req.body.secondPrice,
thirdItem: req.body.thirdItem,
thirdPrice: req.body.thirdPrice,
tBudget: req.body.tBudget
});
Data.save().then(function(Data){
return user.generateAuthToken().then(function(token){
res.header('x-auth',token).send({Data});
});
console.log('token', token);
}).catch(function(e){
res.send(e)
});
}
app.post('/', authenticate, listPost);
in users schema file
// generating the token
// which i use to pass the token
UserSchema.methods.generateAuthToken = function () {
var user = this;
var access = 'auth';
var token = jwt.sign({_id: user._id.toHexString(), access}, 'abc123').toString();
user.tokens.push({access, token});
return user.save().then(function(){
return token;
});
};
its exposed and wired to other files. i think all i need is to pass the token again when the listPost function is fired. so when authenticate.js make a req.header('x-auth) it receives the same token from generateAuthToken().
but i tried it returns undefined.
Problem solved.
turns out i was trying to make a req.cookies.authorization from authenticate.js
var authenticate = function(req, res, next){
var token = req.cookies.authorization;
console.log('authenticate.js',token);
User.findByToken(token).then(function(user){
if(!user){
return Promise.reject();
}
req.user = user;
req.token = token;
next();
}).catch(function(e){
res.status(401).send();
});
}
when i login using userLogin function but when i try to do anything related to the app after logging in i didn't send the same token via res.cookies.authorization
resulting in authenticate.js to recieve nothing. from the cookies. when i wired every function in the app to send the user token everytime it fires, that fixed the problem.
function listPost(req, res){
var Data = new budgetCalculator({
_creator:req.user._id,
_id: req.body._id,
firstItem: req.body.firstItem,
firstPrice: req.body.firstPrice,
secondItem: req.body.secondItem,
secondPrice: req.body.secondPrice,
thirdItem: req.body.thirdItem,
thirdPrice: req.body.thirdPrice,
tBudget: req.body.tBudget
});
Data.save().then(function(Data){
return Data.generateAuthToken();
}).then(function(token){
res.cookie('authorization', token).send({Data}); // this part was missing
console.log('token', token);
}).catch(function(e){
res.send(e)
});
}
app.post('/', authenticate, listPost);