I am learning NodeJS, and I am having trouble understanding why my middleware is always executed.
From my understanding, the middleware as I wrote it should be executed for all the routes declared after the middleware itself.
My index.js is something like this:
const express = require('express');
const mongoose = require('mongoose');
const router = express.Router();
const bodyParser = require('body-parser'); // Parse incoming request bodies in a middleware before your handlers, available under the req.body property.
const configdb = require('./config/db_develop');
const path = require('path');
const authentication = require('./routes/authentication')(router); // Import Authentication Routes
const noNeedForAuth = require('./routes/noNeedForAuth')(router);
const app = express();
const port = 30000;
mongoose.Promise = global.Promise;
mongoose.connect(configdb.uri, (err) => {
if (err) {
console.log('Could not connect to database ' + err);
} else {
console.log('Connected to the database ' + configdb.db);
}
});
app.use(bodyParser.urlencoded({
extended: false
})); // parse application/x-www-form-urlencoded
app.use(bodyParser.json()); // parse application/json
app.use(express.static(__dirname + '/frontend/buildpath'));
app.use('/noNeedForAuth', noNeedForAuth);
app.use('/users', authentication);
app.get('*', (req, res) => {
res.send(path.join(__dirname + '/client/dist'));
});
app.listen(port, () => {
console.log('Listening on port ' + port + '!');
});
The authentication.js is:
const User = require('../models/user'); // Import User Model Schema
const config = require('../config/db_develop.js'); // Import database configuration
const jwt = require('jsonwebtoken'); // Compact, URL-safe means of representing claims to be transferred between two parties.
module.exports = (router) => {
router.post('/register', (req, res) => {
//Register
});
router.post('/login', (req, res) => {
//Login
});
// MIDDLEWARE
router.use((req, res, next) => {
const token = req.headers['authorization']; // Create token found in headers
// Check if token was found in headers
if (!token) {
res.status(403);
res.json({
success: false,
message: 'No token provided'
}); // Return error
} else {
// Verify the token is valid
jwt.verify(token, config.secret, (err, decoded) => {
// Check if error is expired or invalid
if (err) {
res.json({
success: false,
message: 'Token invalid: ' + err
}); // Return error for token validation
} else {
req.decoded = decoded; // Create global variable to use in any request beyond
next(); // Exit middleware
}
});
}
});
/* ===============================================================
Route to get user's profile data
=============================================================== */
router.get('/profile', (req, res) => {
//Profile, protected route
});
return router; // Return router object to main index.js
}
And my noNeedForAuth.js is
module.exports = (router) => {
/* ===============================================================
Route to get all sections' names
=============================================================== */
router.get('/something', (req, res) => {
// Do something
res.json({
message: 'foobar'
});
});
return router; // Return router object to main index.js
}
From my understanding, a query to /noNeedForAuth/something should be executed without passing from the middleware, so without the need for Authentication. But this is not happening, the middleware is executed first, always.
What am I missing?
Thanks
You are applying your middleware without any mount path to your router. It will execute for any route.
Try something like:
// MIDDLEWARE
router.use('/protected', (req, res, next) => {
const token = req.headers['authorization']; // Create token found in headers
// Check if token was found in headers
if (!token) {
res.status(403);
res.json({
success: false,
message: 'No token provided'
}); // Return error
} else {
// Verify the token is valid
jwt.verify(token, config.secret, (err, decoded) => {
// Check if error is expired or invalid
if (err) {
res.json({
success: false,
message: 'Token invalid: ' + err
}); // Return error for token validation
} else {
req.decoded = decoded; // Create global variable to use in any request beyond
next(); // Exit middleware
}
});
}
});
All your routes, where the user have to be authenticated, are behind /protected.
Related
Working locally but when running prod build, I get the 401 error. Not sure what I am missing. I am having {message: "Invalid Token"} whenever I tried to make a call to any api within the app.
Server.js
require('rootpath')();
const express = require('express');
const app = express();
const cors = require('cors');
const bodyParser = require('body-parser');
const jwt = require('./_helpers/Jwt');
const errorHandler = require('_helpers/Error-handler');
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(cors());
// use JWT auth to secure the api
app.use(jwt());
// api routes
app.use('/users', require('./users'));
// global error handler
app.use(errorHandler);
// start server
const port = process.env.NODE_ENV === 'production' ? (process.env.PORT || 80) : 4000;
if (process.env.NODE_ENV === 'production') {
app.use(express.static('../portal/dist'));
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'portal', 'dist', 'index.html'))
})
}
const server = app.listen(port, function () {
console.log('Server listening on port ' + port);
});
JWT.js
const expressJwt = require('express-jwt');
const config = require('../config.json');
const userService = require('../users/user.service');
module.exports = jwt;
function jwt() {
const secret = app.set('JWT_TOKEN', (process.env.JWT_TOKEN)) ;
return expressJwt({ secret }).unless({
path: [
// public routes that don't require authentication
'/users/authenticate',
'/users/register'
]
});
}
async function isRevoked(req, payload, done) {
const user = await userService.getById(payload.sub);
// revoke token if user no longer exists
if (!user) {
return done(null, true);
}
done();
};
Error handler.js
module.exports = errorHandler;
function errorHandler(err, req, res, next) {
if (typeof (err) === 'string') {
// custom application error
return res.status(400).json({ message: err });
}
if (err.name === 'ValidationError') {
// mongoose validation error
return res.status(400).json({ message: err.message });
}
if (err.name === 'UnauthorizedError') {
// jwt authentication error
return res.status(401).json({ message: 'Invalid Token' });
}
// default to 500 server error
return res.status(500).json({ message: err.message });
}
config.js
{
"secret": "Gu_*s+dF]x$E~n2B:#FwS.&Y;#M:sLMQ"
}
Added the interceptor into the app module. Not sure if I am missing something.
You need to provide isRevoked to the jwt instance
return expressJwt({ secret, isRevoked })
I am using JWT to generate a token for access control. I can hit /api/auth/login and get back the token, however, when attempting to hit /api/protected with a GET request, I get 401 Unauthorized.
I've looked through SO and haven't found anything specific although it seems like a routine issue, maybe. I have tried moving the route around in the server.js file to see if that is the issue . I have removed the preceeding slash from the route (from /api/protected to api/protected) and using the latter I get back a bunch of html due to, I think, the app.use(express.static....
I am using Postman to test it but i'm not sure what I'm missing here. I have also made sure to set the authorization to Bearer Token in Postman.
'use strict';
const { Strategy: LocalStrategy } = require('passport-local');
// Assigns the Strategy export to the name JwtStrategy using object destructuring
const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');
const { User } = require('../users/models');
const { JWT_SECRET } = require('../config');
const localStrategy = new LocalStrategy((username, password, callback) => {
let user;
User.findOne({ username })
.then(_user => {
user = _user;
if (!user) {
// Return a rejected promise so we break out of the chain of .thens.
// Any errors like this will be handled in the catch block.
return Promise.reject({
reason: 'LoginError',
message: 'Incorrect username or password'
});
}
return user.validatePassword(password);
})
.then(isValid => {
if (!isValid) {
return Promise.reject({
reason: 'LoginError',
message: 'Incorrect username or password'
});
}
return callback(null, user);
})
.catch(err => {
if (err.reason === 'LoginError') {
return callback(null, false, err);
}
return callback(err, false);
});
});
const jwtStrategy = new JwtStrategy(
{
secretOrKey: JWT_SECRET,
// Look for the JWT as a Bearer auth header
jwtFromRequest: ExtractJwt.fromAuthHeaderWithScheme('Bearer'),
// Only allow HS256 tokens - the same as the ones we issue
algorithms: ['HS256']
},
(payload, done) => {
done(null, payload.user);
}
);
module.exports = { localStrategy, jwtStrategy };
'use strict';
//How does order of code affect how it works?
// YES
require('dotenv').config();
const express = require('express');
const mongoose = require('mongoose');
const morgan = require('morgan');
const passport = require('passport');
const path = require('path');
const { router: usersRouter } = require('./users');
const { router: authRouter, localStrategy, jwtStrategy } = require('./auth');
mongoose.Promise = global.Promise;
// Is this needed if dotenv is in this file also?
const { PORT, DATABASE_URL } = require('./config');
const app = express();
// Logging
app.use(morgan("common"));
// const logRequest = (req, res, next) => {
// const now = new Date();
// console.log(
// `local log - ${now.toLocaleDateString()} ${now.toLocaleTimeString()} ${req.method} ${req.url}`
// );
// next();
// }
app.use(function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Content-Type,Authorization');
res.header('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE');
if (req.method === 'OPTIONS') {
return res.send(204);
}
next();
});
passport.use(localStrategy);
passport.use(jwtStrategy);
//app.use(logRequest);
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use('/api/users/', usersRouter);
app.use('/api/auth/', authRouter);
app.use("/api/items", require('./routes/api/items'));
// protected route that needs a valid JWT for access
const jwtAuth = passport.authenticate("jwt", { session: false });
// route to handle static content ie.e *.jpg
app.use(express.static(path.join(__dirname, "client", "build")));
app.get('/api/protected', jwtAuth, (req, res) => {
return res.json({
data: 'Hello World'
});
});
// have react client handle all additional routes
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "client", "build", "index.html"));
});
let server;
function runServer(DATABASE_URL, port = PORT) {
return new Promise((resolve, reject) => {
// How is DATABASE_URL used? What is the value? Is it referencing
// DATABASE_URL?
mongoose.connect(DATABASE_URL, { useNewUrlParser: true, useFindAndModify: false }, (err) => {
console.log("Success");
if (err) {
return reject(err);
}
server = app.listen(port, () => {
console.log(`Your app is listening on port ${PORT}`);
resolve();
})
.on('error', (err) => {
mongoose.disconnect();
reject(err);
});
});
});
}
function closeServer() {
return mongoose.disconnect()
.then(() => new Promise((resolve, reject) => {
console.log("Closing server");
server.close((err) => {
if (err) {
return reject(err);
}
resolve();
});
}));
}
if (require.main === module) {
runServer(DATABASE_URL)
.catch(err => console.error(err));
}
module.exports = { app, runServer, closeServer };
enter code hereI am expecting to get back a string that says "Hello World" just to make sure i'm hitting the endpoint correctly. Instead I get the 401 error, GET /api/protected HTTP/1.1" 401enter code here
I am trying to get user object inside the req, so I can have it on all my routes. This is my setup:
app.js:
// Use the passport middleware
app.use(passport.initialize());
// load passport strategies
const localSignupStrategy = require('./server/passport/local-signup');
const localLoginStrategy = require('./server/passport/local-login');
passport.use('local-signup', localSignupStrategy);
passport.use('local-login', localLoginStrategy);
// View engine setup
app.set('views', path.join(__dirname, '/server/views'));
app.set('view engine', 'pug');
// Serve static assets normally
app.use(express.static(path.join(__dirname, '/dist')));
// Define routes
app.use('/auth', auth); //Auth controller
app.use('/api', api);
Route for Auth controller:
const express = require('express');
const router = express.Router();
const authController = require('../main/controllers/authController');
// POST /auth/signup
router.post('/signup', authController.postSignup);
// POST /auth/login
router.post('/login', authController.postLogin);
module.exports = router;
authController.postLogin
exports.postLogin = function(req, res, next) {
const validationResult = validateLoginForm(req.body);
if (!validationResult.success) {
return res.status(400).json({
success: false,
message: validationResult.message,
errors: validationResult.errors
});
}
return passport.authenticate('local-login', (err, token, userData) => {
if (err) {
if (err.name === 'IncorrectCredentialsError') {
return res.status(400).json({
success: false,
message: err.message
});
}
return res.status(400).json({
success: false,
message: 'Could not process the form.'
});
}
return res.json({
success: true,
message: 'Login success.',
token,
user: userData
});
})(req, res, next);
};
This is my normal controller route:
// GET /api/cms
router.get('/cms/', authCheck(), getCmsDataController.getCmsData);
module.exports = router;
authcheck.js
module.exports = function(roles) {
// Return middleware
return (req, res, next) => {
if (!req.headers.authorization) {
return res.status(401).end();
}
// Get the last part from a authorization header string like "bearer token-value"
const token = req.headers.authorization.split(' ')[1];
// Decode the token using a secret key-phrase
return jwt.verify(token, config.jwtSecret, (err, decoded) => {
// 401 not unauthorized
if (err) return res.status(401).end();
const userId = decoded.sub;
// Check if user exists
return User.findById(userId, (err2, user) => {
if (err2 || !user) return res.status(401).end();
req.currentLoggedUser = user;
console.log(user.role);
if (roles) {
if (roles.indexOf(user.role) > -1) return next();
else return res.status(401).end();
}
return next();
});
});
};
};
And the controller itself:
// GET /api/cms-data/
exports.getCmsData = function(req, res, next) {
return res.json({
message: 'Lets see does this thing work or not!!!'
});
};
Issue is when I reach the getCmsData controller, I would like to have a user object inside the req object. My user has some properties like role and gender, which I need access to. I have one hacky solution, but I think there is a way to do that.
Could you create a middleware function for this purpose:
function getRequestUser(req) {
// In reality you'd load from data store based on request.
return {id: 1, name: "Jim Smith"};
}
function addUserMiddleWare(req, res, next) {
req.user = getRequestUser(req);
next();
}
// Then add it to your route.
// GET /api/cms
router.get('/cms/', authCheck(), addUserMiddleWare, getCmsDataController.getCmsData);
module.exports = router;
// Or, apply to all paths on router
router.use(addUserMiddleWare);
I am using jwtwebtoken, express, mongo, nodejs, bcrypt for my backend APIs. I notices that the router that i have for localhost:3000/account/update path is getting executed but it returns 404 after successful operation. Db connection is fine. but the localhost:3000/account/update route in postman gets 404 Why?
server.js
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const dbUtils = require('./services/dbconnect');
module.exports = app;
// get an instance of the router for account api routes.
const accountRoutes = require('./services/account');
const registerRoutes = require('./services/register');
const authenticateRoutes = require('./services/authentication');
// used to create, sign, and verify tokens
const jwt = require('jsonwebtoken');
// Secret key to generate new tockens.
app.set('superSecret', "11111"); // secret variable
// parse application/json
app.use(bodyParser.json())
app.use('/account', accountRoutes);
app.use('/authenticate', authenticateRoutes);
app.use('/register', registerRoutes);
app.get('/', (req, res) => res.send('Hello World!'));
dbUtils.connectToServer(() =>{
app.listen(3000, () => console.log('Server listening on port 3000!'));
});
each-request.js
const jwt = require('jsonwebtoken'); // used to create, sign, and verify tokens
const app = require('./../server');
function verifyLoginTokenForEachRequest(req, res, next) {
// check header parameters for token
const token = req.headers['x-access-token'];
try {
if (token) {
// verifies secret and checks exp
jwt.verify(token, app.get('superSecret'), (err, decoded) => {
console.log(decoded);
if (err) {
throw err || new Error('not authorized')
} else {
// if everything is good, save to request for use in other routes
req.decoded = decoded;
console.log("[authorized] user logged in")
next();
}
});
} else {
// if there is no token, return an error
return res.status(403).send({msg: "not authorized"});
}
} catch(e) {
console.log("[unauthorized]", e);
return res.status(401).json({ msg: 'Failed to authenticate token.' });
} finally{
next();
}
}
module.exports={verifyLoginTokenForEachRequest};
account.js
const express = require('express')
const router = express.Router();
const verifyLoginTokenForEachRequest = require('./each-request').verifyLoginTokenForEachRequest;
const dbUtils = require('./dbconnect');
router.use(verifyLoginTokenForEachRequest);
router.post('/update', (req, res) => {
const body = req.body;
const db = dbUtils.getDb();
console.log(body);
try {
db.collection('users')
.updateOne({emailid: body.emailid},{$set:{firstName: body.firstName, lastName: body.lastName, lastModified: body.lastModified}},function(err, doc){
if(err || !doc) {
console.log(err);
throw err || new Error('Failed to update')
} else {
console.log("[success] update success");
console.log(doc)
res.status(200).send();
}
});
} catch(e) {
console.error("[error] failed to update");
res.status(500).send({msg: 'failed to update'});
}
});
module.exports = router;
There are a bunch of issues I see wrong here. I'm not sure which ones exactly do or don't add up to exactly what you asked about, but you need to first clean up these issues and then see what problems are left (if any).
Your verifyLoginTokenForEachRequest() function is always calling next(), even when it's already sent a 401 or 403 response. If you send a response, don't call next(). next() tells Express to keep going to the next routes. Once you've sent a response, you're done. Stop further routing. Don't call next().
In verifyLoginTokenForEachRequest(), you are trying to create an error condition by doing a throw xxx inside an async callback and then expecting to catch that at a higher level. That will not work. The exception will just go back into the bowels of jwt.verify() and will not get to your exception handler. Async exceptions (unless they are part of a promise .then() handler) will go nowhere. You can't throw and catch them at a higher level. You have to deal with them where they occur. In this case, you should just send your error response right there.
In verifyLoginTokenForEachRequest(), you're calling next() in a finally clause when you very well may have already sent the response. Only call next() when you want routing to continue to other route handlers and you have not yet sent a response.
In router.post('/update', ...), you're again throwing an exception inside an async callback which will not be caught by your exception handler. Can't do that. Send the error response there or use promises so you can more easily propagate the error to a higher level.
Here's a fixed up version of verifyLoginTokenForEachRequest():
const jwt = require('jsonwebtoken'); // used to create, sign, and verify tokens
const app = require('./../server');
function verifyLoginTokenForEachRequest(req, res, next) {
// check header parameters for token
const token = req.headers['x-access-token'];
if (token) {
// verifies secret and checks exp
jwt.verify(token, app.get('superSecret'), (err, decoded) => {
console.log(decoded);
if (err) {
console.log("[verifyLoginTokenForEachRequest]", err);
return res.status(401).json({msg: 'Failed to authenticate token.'});
} else {
// if everything is good, save to request for use in other routes
req.decoded = decoded;
console.log("[authorized] user logged in")
next();
}
});
} else {
// if there is no token, return an error
return res.status(403).send({msg: "not authorized"});
}
}
module.exports = {
verifyLoginTokenForEachRequest
};
Here's a fixed up version of account.js:
const express = require('express')
const router = express.Router();
const verifyLoginTokenForEachRequest = require('./each-request').verifyLoginTokenForEachRequest;
const dbUtils = require('./dbconnect');
router.use(verifyLoginTokenForEachRequest);
router.post('/update', (req, res) => {
const body = req.body;
const db = dbUtils.getDb();
console.log(body);
db.collection('users')
.updateOne({emailid: body.emailid},{$set:{firstName: body.firstName, lastName: body.lastName, lastModified: body.lastModified}},function(err, doc){
if(err || !doc) {
console.error("[error] failed to update", err);
res.status(500).send({msg: 'failed to update'});
} else {
console.log("[success] update success");
console.log(doc);
res.end();
}
});
});
module.exports = router;
I have two app.listens in the code and I get an error ,when I take one of them out ,the authentication stops working ,how can I resolve the problem ,I'm trying to implement passport js into my app (I have followed a tutorial and I have passport js example working I just want to implement it to myproject )this is the following error I'm getting
screenshot here
'use strict'
const express = require('express')
const fs = require('fs')
const https =require('https')
const path = require('path')
var app = express();
var bodyParser = require('body-parser');
var morgan = require('morgan');
var mongoose = require('mongoose');
var jwt = require('jsonwebtoken'); // used to create, sign, and verify tokens
var config = require('./config'); // get our config file
var Index = require('./api/planner/index'); // get our mongoose model
var port = process.env.PORT || 3443; // used to create, sign, and verify tokens
mongoose.connect(config.database); // connect to database
app.set('superSecret', config.secret); // secret variable
// use body parser so we can get info from POST and/or URL parameters
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// use morgan to log requests to the console
app.use(morgan('dev'));
// =======================
// routes ================
// =======================
// basic route
app.get('/', function(req, res) {
res.send('Hello! The API is at http://localhost:' + port + '/api');
});
// API ROUTES -------------------
// get an instance of the router for api routes
var apiRoutes = express.Router();
// TODO: route to authenticate a user (POST http://localhost:8080/api/authenticate)
apiRoutes.post('/authenticate', function(req, res) {
// find the user
User.findOne({
name: req.body.name
}, function(err, user) {
if (err) throw err;
if (!user) {
res.json({ success: false, message: 'Authentication failed. User not found.' });
} else if (user) {
// check if password matches
if (user.password != req.body.password) {
res.json({ success: false, message: 'Authentication failed. Wrong password.' });
} else {
// if user is found and password is right
// create a token
var token = jwt.sign(user, app.get('superSecret'), {
expiresIn: 1440 // expires in 24 hours
});
// return the information including token as JSON
res.json({
success: true,
message: 'Enjoy your token!',
token: token
});
}
}
});
});
// TODO: route middleware to verify a token
apiRoutes.use(function(req, res, next) {
// check header or url parameters or post parameters for token
var token = req.body.token || req.query.token || req.headers['x-access-token'];
// decode token
if (token) {
// verifies secret and checks exp
jwt.verify(token, app.get('superSecret'), function(err, decoded) {
if (err) {
return res.json({ success: false, message: 'Failed to authenticate token.' });
} else {
// if everything is good, save to request for use in other routes
req.decoded = decoded;
next();
}
});
} else {
// if there is no token
// return an error
return res.status(403).send({
success: false,
message: 'No token provided.'
});
}
});
// route to show a random message (GET http://localhost:8080/api/)
apiRoutes.get('/', function(req, res) {
res.json({ message: 'Welcome to the coolest API on earth!' });
});
// route to return all users (GET http://localhost:8080/api/users)
apiRoutes.get('/users', function(req, res) {
User.find({}, function(err, users) {
res.json(users);
});
});
// apply the routes to our application with the prefix /api
app.use('/api', apiRoutes);
// we'll get to these in a second
app.get('/setup', function(req, res) {
// create a sample user
var nick = new User({
name: 'Nick Cerminara',
password: 'password',
admin: true
});
// save the sample user
nick.save(function(err) {
if (err) throw err;
console.log('User saved successfully');
res.json({ success: true });
});
});
// =======================
// start the server ======
// =======================
app.listen(port);
console.log('Magic happens at http://localhost:' + port);
const directoryToServe = 'client'
//const port = 3443
app.use('/',express.static(path.join(__dirname,'..',directoryToServe)))
const httpsOptions = {
cert: fs.readFileSync(path.join(__dirname,'ssl','server.crt')),
key: fs.readFileSync(path.join(__dirname,'ssl','server.key'))
}
https.createServer(httpsOptions, app)
.listen(port, function()
{
console.log(`Serving the ${directoryToServe}/directory at https://localhost:${port}`)})
var bodyParser = require('body-parser');
app.use(bodyParser.json());
app
app.get('/', function(request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.end("We're up and running!!!");
});
var plan = require('./api/planner/index');
app.get('/api/planner',plan.index);
app.post('/api/planner',plan.create);
app.put('/api/planner/:id',plan.update);
app.delete('/api/planner/:id',plan.delete);
console.log("Server running at http://127.0.0.1:8000/");
//
This err is probably caused by using a busy port. If you are using ubuntu, you can check the status of ports by lsof -i:portnumber. After running the command you will have a PID.
You can release the port by kill -9 pid.
Windows and mac have similar commands.