I'm trying to write express middleware to check the validity of a JWT in the Authorization header. This seems quite easy but I don't want it to run on all routes (e.g. not on login/signup routers).
So, I'd like to specify in the router declaration that a route should require a valid token. E.g. something like this
const controllers = require('../controllers');
module.exports = (app) => {
app.post('/auth/signup', controllers.auth.signup.post);
app.post('/auth/login', controllers.auth.login.post);
app.get('/teams', controllers.teams.get, {requiresToken:true});
};
Except, .post and .get don't take a third parameter and the controller only takes (req,res,next) parameters so I can't really see a way of passing startic data for each route. I'm sure I'm missing something simple
This is how i created a middleware to pass the data into
module.exports = function(options) {
return function (req, res, next) {
//write your code here
// here you can access options variable
console.log(options.data)
next();
}
}
How you call that middleware is like this
app.use(middleware({'data' : 'Test'}));
To use on route basis
app.post('/userRegistration', middleware({'data' : 'Test'}), (req, res) => {});
You can exclude the auth subroute from this middleware using negative lookup regexp:
const controllers = require('../controllers');
module.exports = (app) => {
app.use(/\/((?!auth).)*/, yourJwtTokenValidatorMethod); // replace with your jwt token validator middleware
app.post('/auth/signup', controllers.auth.signup.post);
app.post('/auth/login', controllers.auth.login.post);
app.get('/teams', controllers.teams.get, {requiresToken:true});
};
Related
So I have added some swagger configuration in a routes folder like so:
import express from 'express';
import swaggerUi from 'swagger-ui-express';
import swaggerDocument from '../swagger/swagger.json';
const router = express.Router();
router.get('/api-docs', swaggerUi.setup(swaggerDocument));
export { router as swaggerRouter }
Now there is an authentication process in the root app.ts file, but my understanding is if I add the swagger endpoint before it executes the authenticate logic, it should not ask for headers:
app.get('/', (req, res) => {
res.send('Howdy!');
});
app.use(swaggerRouter);
app.use(async (req, res, next) => {
const result = await auth.verifyAuth(req).catch((err) => {
return err;
});
if (result.httpCode == 200) {
res.locals.authResult = result
next()
} else {
res.send(result)
}
});
So the authentication logic from where verifyAuth comes from would make for a good middleware to target endpoints instead of the whole entire application, but to refactor it to work as such a middleware is a pain because the author wrote every function to depend on every other function.
And yet, if I add a random endpoint above that authenticate logic like:
router.get('/pingMe', (req, res) => {}
You can go to that one without being asked to provide headers.
What am I missing?
I literally removed the authentication logic and I am still unable to get to the swagger endpoint without being asked for headers.
So I'm not really sure if the title is descriptive enough, but here is a super simple example.
My site has a public area and a restricted admin area.
example.com/admin (admin home page)
example.com/admin/news (news page)
example.com/admin/posts (posts page)
And because I don't want people who aren't administrators or logged in to be able to access it, I have a simple middleware function to check for cookies.
app.js
const express = require('express');
const app = express();
const authMiddleWere = async (req, res, next) => {
// pseudo-code, do some cookie validity check here
console.log(`Route: ${req.url}`)
if (cookie) {
next();
}
};
const adminRouter = require('./routes/private/home');
const newsRouter = require('./routes/private/news');
const postsRouter = require('./routes/private/posts');
app.use('/admin/', authMiddleWere, adminRouter);
app.use('/admin/news', authMiddleWere, newsRouter);
app.use('/admin/posts', authMiddleWere, postsRouter);
/routes/private/home.js
const express = require('express');
const router = express.Router();
router.get('/', async (req, res, err) => {
res.render('private/home');
});
module.exports = router;
The problem here is that this authMiddleWere function gets called twice when I visit nested paths such as example.com/admin/news which shares the same pattern - it's starting with /admin/......
I can tell that for sure because we are logging the req.url in our middleware function so if I go to example.com/admin it will log out:
Route: /
But if I go to example.com/admin/news it will log out both:
Route: /
Route: /news
So what is causing this and how do I work my way around it? I'm assuming that what I described is the intended behavior of Express.js so I am looking for a way to get around this or (re)structure my code better.
Cheers!
You can use a regex for your route.
app.use(/\/admin$/, authMiddlewear, authRouter);
This will match only routes that end in admin. You may need to handle cases where the route is /admin/ instead of /admin, but iirc, express handles that intelligently.
Well one way you can fix this is by creating a separate route file and splitting everything into a MVC manner. For example:
Inside your main app.js just create a route pointing to the /admin like so:
app.use('/admin', authMiddleWere, require('./src/your-route-to-the-file/admin.route'));
Inside the admin.route file, call your controller like this:
const express = require("express");
const router = express.Router();
const mainAdminCtrl = require("../controllers/admin.controller");
router.get("/news", mainAdminCtrl.adminAuthDisplay);
module.exports = router;
Where the const mainAdminCtrl is your controller and the function adminAuthDisplay is your service.
Essentially, you are splitting your functionality in to a dedicated router, controller and service file. So when you try to access the route /admin, it will look for any suffix inside the router file.
In a case where you want to access the /news endpoint, your API will only make the call once.
If this helps, I can expand my explanation further.
I am trying to access my application-level middleware from router in a project generated with express application generator.
Middleware is used to query database with user ID received from router.
I feel like I'm missing something very simple (or fundamental) but can't get around the problem (this being my first Node.js project). So more than best practice I'm looking for a simple solution
I've tried using different app methods including post.
/app.js
var MyAppMidW = function (req, res, next) {
res.send(queryDB(req));
next()
}
app.use(MyAppMidW);
/routes/index.js
router.get("/dbquery", (req, res) => {
if (req.header('auth-header')) {
res.send(req.app.get.MyAppMidW(req.header('auth-header'))); //The problem
}
else {
res.send(req.app.get('defaultData')); //This works
}
});
Error messages include "$middleware is not a function" and "$middleware is not defined".
Solution
/app.js
app.MyAppMidW = function (req) {
queryDB(req);
}
/routes/index.js
router.get("/dbquery", (req, res) => {
if (req.header('auth-header')) {
req.app.MyAppMidW(req.header('auth-header'))); //Makes a database query
res.send(req.app.get('defaultData')); //Fetches database query result
}
else {
res.send(req.app.get('defaultData'));
}
});
If you do it like this
app.use(MyAppMidW);
Every request will query your db, and thats not what you want. I guess you use the MVC design pattern.
In your route folder you have something like this:
import appController from "../controllers/app.js"
router.get("/dbquery", appController.MyAppQuery)
And in your controllers folder you have your logic that querys the db
exports.MyAppQuery = (req, res){
//If u use mongodb for example
YourModel.find().then(data => {
res.json(data)
})
}
You need to call app.set("MyAppMidW", MyAppMidW) and then you can use get. Or do this inside the app.js file
app.MyAppMidW = function (req, res, next) {
res.send(queryDB(req));
next()
}
Then call it by req.app.get('MyAppMidW')(req.header('auth-header')) or req.app.MyAppMidW(req.header('auth-header')) inside the routes file.
But middleware is called automatically when you say app.use(MyAppMidW) the function is called by default on each request. So no need to call it explicitly inside the router function.
I have three files server.js, views.js and access.js
In server.jsI have put all the dependencies and some routes like
app.post('/services/getallversions', (req, res) => {
...
// code to handle the req and send response
})
In views.js I have code like below,
module.exports = function(app, db, bodyParser, rules, constants, query) {
app.post('/services/user/:user/:type', (req, res) => {
// user can be 'abcd'
// type can be addview, deleteview etc.
...
// do processing for the req and send res
})
}
In access.js I have code like,
module.exports = function(app, db, bodyParser, rules, constants, query) {
app.post('/services/user/:user/:type', (req, res) => {
// user can be 'abcd'
// type can be addaccess, removeaccess etc.
...
// do processing for the req and send res
})
}
In server.js file I require the access.js and views.js in following way,
var access = require('./access')(app, db, bodyParser, rules, constants, query)
var views = require('./views')(app, db, bodyParser, rules, constants, query)
When I try to POST using /services/user/abcd/addaccess my views.js file code gets executed. constants, query, rules are other .js file which is already used in server.js using require('./filename').
I understand that the ambiguity causes due to same URL structure. I am using Express 4 and Node JS 6. I want to separate code of access.js and views.js from server.js and put them in separate files and require them in the above mentioned manner. views.js and access.js are created by me. They are not any Javascript Framework or something like that.
In view.js I have also tried the following code
var router = require('express').Router()
router.post('/services/user/:user/:type', (req,res)=>{})
But the same problem exists. Is there any way to achieve the thing ?
I suggest you use "miniApp" concept in Express, where each "miniApp" is distinguished using name-space.
For example:
Main App:
All routes with '/views/...' prefix will go to viewsCtrl. This middleware should appear before your default/main app routes:
var viewsCtrl = require('./views');
app.use('/views', viewsCtrl);
Inside views.js:
var router = require('express').Router();
// complete route /views/services/user/:user/:type
router.get('/services/user/:user/:type', function(req, res){...});
module.exports = router;
Same for access.js.
The routes are identical and express will never be able to tell which one to call. Order is not the problem here; as Chris G said in his comment, the second call to app.post(...) will overwrite the first (think of URLs as keys in a hashset).
You already know that the url will be in the format of /addview or /removaccess etc, so you can put that knowledge in the routing middleware:
// access.js
app.post('/services/user/:user/access/:type', (req, res) => {
// ... type is now only add, remove, etc...
})
// view.js
app.post('/services/user/:user/view/:type', (req, res) => {
// ...
})
or even (I think):
// access.js
app.post('/services/user/:user/:type((access)$)/', (req, res) => {
// ... will match addaccess, removeaccess and so on
// but I'm not entirely sure ...
})
Reference here:
https://expressjs.com/en/guide/routing.html
I've been working with Express 4 and I just tried to use a namespaced route, but I wanted the namespaced route to have a param.
For instance:
/:username/shows
/:username/shows/:showname/episodes
etc etc. So with this, I thought this would be a good fit for express namespacing.
Router = require("express").Router;
userRouter = Router();
userRouter.route("/shows").get(function(req,res){ ... });
app.use("/:param", userRouter);
This would get the page to load as expected at /:username/shows, however the req.params, where I'd normally expect to find a key called username was empty. Am I missing something? Where can I access these params?
In your code, :username param is used by app, not in userRouter.
You can only access that from app, or you can curry :username param information to userRouter by using app.params() function
app.param('username', function(req, res, next, username) {
req.username = req.params.username;
next();
});
app.use('/:username', userRouter);
userRouter.get('/show', function(req, res, next){
var username = req.username; // Here you can access;
res.send("DONE");
});