I made a custom middleware for Express router that allows me to whitelist certain endpoints of my API to be excluded from authentication. However I have a route where I depend on URL parameter and I can't get my middleware to work as intended with it. Apparently :profileId doesn't do anything and my API endpoint still requires authentication.
The reason I need that path to be excluded from authentication is because of my React frontend that should display that data to the public (without people registering and logging in). Any tips how to solve this?
const apiAuth = (req, res, next) => {
let authRequired = true;
if (
req.path == "/api/users/register" ||
req.path == "/api/users/login" ||
req.path == "/api/profiles/:profileId"
) {
authRequired = false;
}
if (authRequired == true) {
// Auth check logic
}
}
There's a few better approaches for handling the requirement of middleware, that are generally used over the method you're suggesting:
Only include your authentication middleware on routes you require it:
const authenticationMiddleware = (req, res, next) => {
// your login check logic
}
router.get('/api/users/me', authenticationMiddleware, (req, res, next) => {
// your route logic, this endpoint now requires you to be logged in, as you have specified your authentication middleware in the declaration,
})
router.get('/api/profiles/:profileId', (req, res, next) => {
// your route logic, this endpoint does not require you to be logged in as you have not put the middleware in the route delcaration
})
Or, add the authentication middleware based on where your routes are called:
router.get('/api/profiles/:profileId', (req, res, next) => {
// your route logic, this endpoint does not require you to be logged as we have not told our router to use the middleware yet
})
router.use(authenticationMiddleware)
router.get('/api/users/me', (req, res, next) => {
// your route logic, this endpoint now requires you to be logged in, as the router has been told to use the middleware at this point.
})
Why these methods? Try and think of all the router or app calls you're making as adding to a stack which express uses to handle calls to your site or API. As it works its way through looks for routes it will call any middlewares it finds on its way.
This solves the issue of having to declare a list or array of routes which do or don't require a particular piece of authentication, etc.
You'll also need to make sure to call next() in your middleware if you want it to work, as this tells express to continue going through all the routes/middleware's it has.
Related
I have a MEAN stack application and using Node.js and Express.js as back-end API.
Assuming I have a 'comments' route as follow
/* GET /comments listing. */
router.get("/", function(req, res, next) {
Comment.find(function(err, comments) {
if (err) return next(err);
res.json(comments);
});
});
And use it in my server like this:
var commentsRouter = require('./routes/comments');
...
app.use('/comments', commentsRouter);
My question is: Is there a way to prevent users to access http://mrUrl/comments in browser and deny the request with probably 403 Forbidden message but at the same time JavaScript file tries to access the same URL will receive a content message (in the example should be res.json(comments);)
Also, would it be possible to enable such a restriction for all routes once, not for each.
Yes, you can use a middleware.
A middleware is a function you can pass before or after the main function you are executing (in this case, GET comments)
the order of the function location matters, what comes first - executes first, and you implement it like so:
app.use(myBrowsingRestrictionMiddlewareFunction) // Runs
app.use('/comments', commentsRouter);
app.use('/account', accountRouter);
You can also use within a route handler:
app.post('/comments', myMakeSureDataIsAlrightFunction, myMainCreateCommentFunction, myAfterStatusWasSentToClientAndIWishToMakeAnotherInternalActionMiddleware);
The properties req, res, next are passed into the function automatically.
which means, myBrowsingRestrictionMiddlewareFunction receives them and you can use them like so:
export function myBrowsingRestrictionMiddlewareFunction(req, res, next) {
if (req.headers['my-special-header']) {
// custom header exists, then call next() to pass to the next function
next();
} else {
res.sendStatus(403);
}
}
EDIT
Expanding regards to where to place the middleware in the FS structure (personal suggestion):
What I like to do is to separate the router from app.js like so:
app.js
app.use('/', mainRouter);
router.js
const router = express.Router();
router.use(middlewareForAllRoutes);
router.use('/comments', commentsRouter);
router.use(middlewareForOnlyAnyRouteBelow);
router.use('/account', accountRouter);
router.use(middlewareThatWillBeFiredLast); // To activate this, remember to call next(); on the last function handler in your route.
commentsRouter.js
const router = express.Router();
router.use(middlewareForAllRoutesONLYFORWithinAccountRoute);
route.get('/', middlewareOnlyForGETAccountRoute, getAccountFunction);
router.post('/', createAccount);
From what I have read here and here, the order in which you place your middleware function matters, as you can have certain routes not go through the middleware function if it is placed before the route, and the routes which are placed after will go through this middleware function.
I am seeing mixed results as my dev environment is not respecting this and my prod environment is. The code is exactly the same.
What I am trying to do is have my login route not be protected by a token checker middleware function and have the rest of my routes protected by a token.
Here is my code:
routes.get('/login', function(req, res) {
// login user, get token
});
routes.use(function(req, res, next) {
// check header or url parameters or post parameters for token
var token = req.headers['access-token'];
// decode token
if (token) {
// validate token
}
else if (req.method === 'OPTIONS') {
next();
}
else {
// if there is no token
// return an error
return res.status(403).send({
success: false,
message: 'No token provided.'
});
}
});
routes.get('/query/:keywords', function(req, res) {
console.log(req.params.keywords);
// execute query
});
app.use('/', routes);
the /query route is the only one that should have to go through the token middleware function correct? Right now I am getting the /login route also going through the token middleware function, which doesn't make sense as I shouldn't need to have a token to login.
Better yet, if there is a way to target which routes I want protected and which routes I do not want protected, this seems better than having to rely on an "order" of where the middleware function is placed.
First, follow along this usage in ExpressJS:
More than one callback function can handle a route (make sure you specify the next object). For example:
app.get('/example/b', function (req, res, next) {
console.log('the response will be sent by the next function ...')
next()
}, function (req, res) {
res.send('Hello from B!')
})
You'll notice it's definition is close to what you're declaring on routes.use(yourFunction(...)). However, there's no real reason to do it this way other than following examples you've seen in documentation, which is a good way to start nevertheless.
However, it's a flimsy implementation, express will allow hierarchies within it's .get() .post() methods, that's correct, but this is a use case specific and not what you're looking for.
What you need is to implement your custom auth process using the double callback configuration. do this:
// You can save this function in a separate file and import it with require() if you want
const tokenCheck = function(req, res, next) {
// check header or url parameters or post parameters for token
var token = req.headers['access-token'];
// decode token
if (token) {
// validate token
}
else if (req.method === 'OPTIONS') {
next();
}
else {
// if there is no token
// return an error
return res.status(403).send({
success: false,
message: 'No token provided.'
});
}
});
routes.get('/login', function(req, res) {
// login user, get token [Unprotected]
});
routes.get('/query/:keywords', tokenCheck, function(req, res) {
console.log(req.params.keywords);
// execute query [Protected with tokenCheck]
});
app.use('/', routes);
You might need to play around with the code above, but it'll guide you on the right direction, this way, you can specify particular routes to execute the tokenCheck(req, res, next) function as you want.
The easiest way to do this is to use Router Middleware to scope Routes that require Authentication and the routes that don't. Since all Routers are Middleware, we can implement them just like any other middleware. Ensuring that we place the Routers and Routes in the order that we would like our Routes to be evaluated.
In the below example, the Express server has 2 routers, a LoginRouter and an ApiRouter.
LoginRouter - Generates a Token when receiving a request to POST /login and returns that to the requester for subsequent use in the /api routes.
ApiRouter - Wraps all other routers, centralizes middleware that needs to be globally applied to all routes under /api. Is only accessible to Authenticated Requests.
The API Router is only accessible if there is a token included in the Header and that token is obtained from the LoginRouter. LoginRouter has no authentication required.
With this setup, you'll keep adding routers after the Authorization Middleware to the API Router via .use() on the ApiRouter.
The below pattern of composing Routers from other Routers is very powerful, scalable and easy to maintain.
server.js
const express = require('express')
const bodyParser = require('bodyParser')
const ApiRouter = require('./routes/api')
const LoginRouter = require('./routes/login')
const port = process.env.PORT || 1337
const server = express()
server.use(bodyParser.json())
server.use('/login', LoginRouter)
server.use('/api', ApiRouter)
server.listen(port, () => console.log(`Listening on ${port}`))
LoginRouter - /routes/login.js
const router = require('express').Router()
router.post('/', (req, res) => {
// Validate Credentials
// some validation code...
// Then create the token for use later in our API
let token = '...'
// Response 200 OK with the token in the message body
return res.status(200).send({token})
})
module.exports = router
ApiRouter - /routes/api/index.js
const router = require('express').Router()
const UsersRouter = require('./routes/api/users')
router.use((req, res, next) => {
let authorizationHeader = req.headers['authorization'] || req.headers['Authorization'] // handle lowercase
let [, token] = authorizationHeader.split(' ')
if (!token) {
return res.sendStatus(403) // Forbidden, you're not logged in
} else {
// validate the token
if (!tokenIsValid) {
return res.sendStatus(403) // Forbidden, invalid token
}
// Everything is good, continue to the next middleware
return next()
}
})
router.use('/users', UsersRouter)
module.exports = router
UsersRouter - /routes/api/users
const router = require('express').Router()
router.get('/', (req, res) => {
// We only get here if the user is logged in
return res.status(200).json({users: []})
})
module.exports = router
The application of the token middleware should not happen to the login route due to route order and the fact the login route never calls the next object. Without more information we really can't trouble shoot what is happening beyond that however you could try inspecting it in your dev environment with a debugger break and looking at the req that hits that middleware.
We can however give you some information on how to try and isolate your .use middleware and how application of middleware order applies so that you can try and separate it from the login route entirely like in the bottom of your question.
When applying middleware to only specific routes you should keep note that order and .use are for middleware that should answer the request before telling express to continue looking for other middleware that come after them in the router that will also handle the request. If you only want it on a few routes, you can add it to only a few routes by being explicit like so:
router.get('/route', [ middleware1, middleware2, ..., middlewareX])
or
router.get('/route', middleware1, middleware2, ..., middlewareX)
both patterns will work. I however find the array pattern a little more palatable since I can define a lot of middle wares I want to apply and then concatenate new middleware for specific logic, and I only need modify where I declare that concatenation to add more functionality. It'd however rare to need that many middleware and you should be able to use either.
You could also section that middleware off to a subset of routes by using a router and applying it as the first middleware to the route chain before the router.
app.use('/user', authentication, userRouter)
or you can put it inside the router as the first middleware with a .use so that it handles all requests.
So remember the general tips about middleware usage:
order matters for middleware application
optional middleware that should be applied on route basis should be applied with the other middleware in order for only that route
error handling middleware must always come last, and have four arguments (err, req, res, next)
use routers to section .use middleware to specific routes and sets of routes
You can find more information about it in the expressjs documentation for middleware
router.use((req, res, next) => { // export as single route in a file
if (req.isAuthenticated()) {
next();
return;
}
res.sendStatus(401);
});
const authenticate = (req, res, next) => {
if (req.isAuthenticated()) {
next();
return;
}
res.sendStatus(401);
};
The above are 2 ways of writing the authentication route for use in another route (like below). Which way is preferred and why?
router.post('/', authenticate, (req, res, next) => {});
The above way will affect all the requests that your ExpressJS app is
serving, where as the second approach uses Object oriented scripting
way to authenticate only those requests that require authentication.
Say your are writing a sign in or sign up API, you wouldn't need an authentication parameter for that unless mentioned otherwise.
****UPDATE****
The first approach will affect all the requests that your router is serving.
You probably have used the router in your App.js file as
const myRoute = require('./routes/test'); // where `test.js` is a file in routes folder with your code above
app.use('/some_route', myRoute);
All requests going to http://servername:port/some_route/.... will be filtered in your test.js file now.
If I do this:
app.get('/test', (req, res) => {
res.send('First');
});
app.get('/test', (req, res) => {
res.send('Second');
});
The first call is what works, the second get call does not override the first. Is there a good way to change that?
Basically I am working on a Swagger app where it can hit multiple APIs. I forked from https://github.com/thanhson1085/swagger-combined. The app knows if API A hits /user it will proxy any calls from /user to the appropriate place. Right now the app lists all API calls from as many APIs as you load. That means if API A & API B have the same endpoint of /user, I will only ever proxy to the first API that registered the endpoint in my app.
Express does not allow to override routes. But you should be able to use one and decide where to proxy those calls.
app.get('/test', (req, res) => {
if(req.isFirst()){
res.send('First');
}
if(req.isSecond()){
res.send('Second');
}
});
It would probably better to have those apis in separate endpoints like /api1/user/ and api2/user/.
You can basically pass Express Router into another Express Router.
let api1Router = express.Router()
rootRouter.use('/api1', api1Router)
Hope that helps.
I sort of creating a framework currently using express and part of it is the have default routing.
But I also need to on occasion replace the default routing.
Turned out that it was not superhard to do, it's just a matter of moving the default route to the end, and then, if anything else get a hit before it will be used instead.
const router = require('express').Router();
// default rout
router.all('/:path(\\d+|\\w+)?', (req, res, next) => {
}
// Route I want to be used first
router.get('/list', (req, res, next) => {
}
// move default route to end of stack
const stack = router.stack;
for(let i = 0; i < stack.length; i++) {
if(i < stack.length -1 && stack[i].regexp.toString() === '/^(?:\\/(\\d+|\\w+))?\\/?$/i' && stack[i].route.methods._all) {
stack.push(stack.splice(i, 1)[0]);
}
}
I have my application structured with 3 Routes (api, admin, default). Each lives in there own file and has it's own middleware and exports a Route. The problem I am facing is when I want to forward to another route that lives on a different router. Essentially I want to call the same function so that I am not serving up the same view from multiple locations.
I don't want to user res.redirect('/someplace') because I want to be able to pass the req and res objects on to the method.
|-app.js
|-routes
|---admin.js
|---api.js
|---default.js
The routes are required and used in app.js as follows
app.use('/api', require('./routes/api')(passport);
app.use('/admin', require('./routes/admin')(passport);
app.use('/', require('./routes/default')(passport);
Inside of admin if have a situation where I need redirect to login and pass some data
// authenticates all routes for the admin router
router.use(function(req, res, next){
if(req.isAuthenticated()){
return next();
}
res.flashMessage.push('Session expired'); //is lost after redirect
res.redirect('/login');
//do I need to restructure my whole app so that I don't
//have to call res.redirect('login')
});
Any ideas on how to structure this? Do I need to export every method and keep all of my routes in one router file? That doesn't very clean, but if the functions are somewhere else it may be too messy.
You can forward it by calling the next callback ,but only if you do not use any paths.
app.use(function(req, res, next) {
// ... api
next();
});
app.use(function(req, res, next) {
// ... admin
next();
});
Another option is use * that will match all paths:
app.use("*", function(req, res, next) {
var path = req.path; // just example how it can be done
if (path === "/api") {
// ...
path = "/admin";
}
if (path === "/admin") {
// ...
}
});
Edit:
I don't think that express has something like next('/login'); ,so basically function that can forward a request to another path and I don't think that is right to have something like this. If a client ask for /admin you should send this particular page and not the page that is under /login. If you want to send back to a client the login page than just redirect it as you did it in your question. I understand that you want to keep the req, res ,but then is the problem in the proposal/structure of your webapp.