4

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.

farbodg
  • 675
  • 4
  • 14
  • 25
  • The route applying to the one before should never happen... Like never. JavaScript is procedural at that point and the global middleware should not apply to the login route... Is there any error output you can provide? – Robert Mennell Jun 19 '18 at 23:21
  • 1
    My guess is that it's not the `/login` request that is hitting your token checking middleware. You should do a `console.log(req.url)` in that middleware to see what it is that's hitting it. It very well could be something like a request for a favicon resource. Unless you call `next()` in your `/login` route handler, no further routing will take place after it matches `/login`. – jfriend00 Jun 19 '18 at 23:55

3 Answers3

5

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.

Gabriel Balsa Cantú
  • 1,954
  • 1
  • 14
  • 11
1

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
peteb
  • 18,552
  • 9
  • 50
  • 62
0

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:

You can find more information about it in the expressjs documentation for middleware

Robert Mennell
  • 1,954
  • 11
  • 24
  • Thanks for your response, is there a way to apply middleware to all functions globally, but then have a do not apply to list, in the case where you only have say a few out of 30 routes that you don't need middleware applied to? – farbodg Jun 19 '18 at 23:22
  • Who said that `.use()` should only be used when affecting the entire router? I disagree with this totally and I think you should use the pattern that fits the use case. – peteb Jun 19 '18 at 23:22
  • 1
    @peteb .use will answer any and all requests that reach it. I have clarified my answer to reflect that – Robert Mennell Jun 19 '18 at 23:24
  • @farbodg You can layer your Routers to do this if you'd like or you can use the [`express-unless`](https://www.npmjs.com/package/express-unless) package to specify middleware that applies to everything unless it matches the specified pattern. Its important to keep your routers as small as possible and leverage Router Middleware composition if you want to get the most out of Express. – peteb Jun 19 '18 at 23:24
  • @forbodg in those cases you should use a router so you can specify in the router where it should and should not apply those middlewares and will handle all requests to the base route the router answers too. – Robert Mennell Jun 19 '18 at 23:27
  • This is not really correct. A `router.get()` route will match BEFORE an `router.use()` that comes after it and the `router.use()` will not get called on that route unless the `router.get()` handler calls `next()` to tell Express to continue routing. – jfriend00 Jun 19 '18 at 23:41
  • @jfriend00 are you asking me to clarify between a terminating middleware and a pass through middleware? – Robert Mennell Jun 19 '18 at 23:43
  • @jfriend00 What you are referring to is exactly what I was referring to in my comment as well. – peteb Jun 19 '18 at 23:50
  • Explanations for down votes are helpful so that we as people writing answers can improve our answer – Robert Mennell Jun 19 '18 at 23:53
  • 1
    @RobertMennell - I'm saying that your bit about "global" middleware is just wrong. Routes (including middleware) are simply matched in the order they were defined and when one matches, no further routes are called unless the matched route explicitly calls `next()` to tell Express to continue looking for matches. So, your answer is just wrong. I have now posted multiple comments explaining my downvote. – jfriend00 Jun 19 '18 at 23:53
  • @jfriend00 I can clarify that more if you'd like. I thought that was adressed when I changed the line to 'Order and .use are for middleware that affect other middleware that come after them in the router that are also handling the request.' but if that's not clear enough I can go very verbose if you'd like – Robert Mennell Jun 19 '18 at 23:55
  • 1
    Your solution is not the preferred solution to the OP's problem. Yes, you could attach middleware to every single route, but that is not what the OP is trying to do here and this is not the preferred solution for what they are trying to do. Also, you didn't tell use you edited anything so I have no idea when you edit. I don't get an "edit" notification. – jfriend00 Jun 19 '18 at 23:56
  • @jfriend00 from question: 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. – Robert Mennell Jun 19 '18 at 23:57
  • Well, "order" is an extremely valuable feature of routing and using it when applicable can vastly, vastly simplify route handling. And, the OP's specific case is exactly a problem where order makes perfect sense and anything else is just more complicated. – jfriend00 Jun 19 '18 at 23:58
  • I also have a section detailing that routers should be used to also section off routes that certain middleware should apply to on a "global"(to the router) basis – Robert Mennell Jun 19 '18 at 23:58
  • @jfriend00 I would be inclined in that SPECIFIC CASE ONLY but if they continue to add routes should we not give them information on tools that would allow them to apply only where they would want it? – Robert Mennell Jun 20 '18 at 00:03
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/173438/discussion-between-robert-mennell-and-jfriend00). – Robert Mennell Jun 20 '18 at 00:09
  • 1
    @RobertMennell - That's fine information, but not what the OP is asking. I'm done discussing with you. I've left a comment on the original question that the OP has not responded to with my guess as to what their problem actually is. I'm awaiting their response. Don't feel like I need to discuss this with you further. I removed my downvote based on your edits, but I don't think your answer actually tells them what their problem is - it's just another, less general, less recommended way of doing things that does not attempt to explain why they have an issue with the current code. – jfriend00 Jun 20 '18 at 00:16