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.
Related
Using Nodejs and an Express server I'm trying to prevent anyone from reaching my second set of routes without logging in and while this works I get stuck in a redirect loop if the session doesn't detect the email in the session token. I believe its trying to check for the req.session.email for the /users endpoint as well causing the redirect loop but as the session checking middleware is used after I thought the /users endpoints would avoid the check.
How can I organize my code so that the books endpoints can only be reached when the req.session.email is satisfied and also not get stuck in a redirect loop when someone tries to reach it without being logged in?
app.use('/users', users)
app.use((req, res, next) => {
if(!req.session.email){
res.redirect('/login')
}
else{
next();
}
})
app.use('/books', books);
The order of the app.use statements is not really important in this case; You could add your middleware to the route-level if you're only checking the /books endpoint.
const yourMiddlewareFunction = (req, res, next) => {
if(!req.session.email){
res.redirect('/login')
}
else{
next();
}
}
app.use('/books', yourMiddlewareFunction, books);
If you only want to protect the /books endpoints, you can do it like this :
function requireLogin(req, res, next) {
if(!req.session.email){
res.redirect('/login')
}
else{
next();
}
}
app.use('/books', requireLogin, books);
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.
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
I'm building an API with Node.js, and I have some endpoints I want to secure.
For simplicity let's assume I'm using HTTP basic authentication (passport-http) for all of my endpoints.
What I'd like to do on top of that, is to make sure that a route like this: api.example.com/users/:uid/ is only accessible by a user with that ID.
I can do it with something like this:
app.get('/users/:uid',
passport.authenticate('basic', {
session: false
}),
function (req, res, next) {
if (req.params.uid !== user.id) {
return next(new Error('Unauthorized'));
}
return next();
},
function (req, res, next) {
// do secret stuff
}
);
But I wonder if there's a way to do this without adding additional middleware, by using Passport itself:
app.get('/users/:uid',
passport.authenticate( ??? ),
function (req, res, next) {
// do secret stuff
}
);
Is it possible? If not, is there a better way?
You can try something perhaps like this. General description: authenticate all requests that hit anything under the /users route as requiring authentication. On your specific route, use some middleware that makes sure that the user trying to access the specific route is the one in the route itself via that uid.
function authorizeUser(req, res, next) {
if (req.user.uid !== req.params.uid) next(new Error('Not your profile!'));
next();
}
// Require login for entire /users section
app.use('/users', passport.authenticate('basic', { session: false }));
// Authorize /users/:uid section to one user
app.use('/users/:uid', authorizeUser);
// Nested routes will all be secured by the middleware above.
app.get('/users/:uid', function (req, res) {
// Secret stuff
});
app.get('/users/:uid/foo/bar', function (req, res) {
// Also secret
});
If you're only securing one endpoint, you can just put it all on the same route.
I'm using Node.js with express and already know the existence of response.redirect().
However, I'm looking for more of a forward() functionality similar to java that takes the same parameters as redirect, but internally forwards the request instead of having the client perform the redirect.
To clarify, I am not doing a proxy to a different server. I'd like to forward('/other/path') directly within the same app instance
It wasn't apparently obvious how to do this from the express documentation. Any help?
You just need to invoke the corresponding route handler function.
Option 1: route multiple paths to the same handler function
function getDogs(req, res, next) {
//...
}}
app.get('/dogs', getDogs);
app.get('/canines', getDogs);
Option 2: Invoke a separate handler function manually/conditionally
app.get('/canines', function (req, res, next) {
if (something) {
//process one way
} else {
//do a manual "forward"
getDogs(req, res, next);
}
});
Option 3: call next('route')
If you carefully order your router patterns, you can call next('route'), which may achieve what you want. It basically says to express 'keep moving on down the router pattern list', instead of a call to next(), which says to express 'move down the middleware list (past the router)`.
You can implement forward (aka rewrite) functionality by changing request url property and calling next('route').
Note that the handler performing forward needs to be configured before other routes which you perform forwards to.
This is example of forwarding all *.html documents to routes without .html extension (suffix).
function forwards(req, res, next) {
if (/(?:.+?)\.html$/.test(req.url)) {
req.url = req.url.replace(/\.html$/, '');
}
next('route');
}
You call next('route') as the last operation. The next('route') passes control to subsequent routes.
As mentioned above, you need to configure forwards handler as one of the first handlers.
app.get('*', forwards);
// ...
app.get('/someroute', handler);
The above example will return the same content for /someroute as well as /someroute.html. You could also provide an object with a set of forward rules ({ '/path1': '/newpath1', '/path2': '/newpath2' }) and use them in forward mechanism.
Note that regular expression used in forwards function is simplified for mechanism presentation purposes. You would need to extend it (or perform check on req.path) if you would like to use querystring parameters etc.
I hope that will help.
For Express 4+
Using the next function does not work if the next handler is not added in the right order. Instead of using next, I use the router to register the handlers and call
app.get("/a/path", function(req, res){
req.url = "/another/path";
app.handle(req, res);
}
Or for HTML5 mode of React/Angular
const dir = process.env.DIR || './build';
// Configure http server
let app = express();
app.use('/', express.static(dir));
// This route sends a 404 when looking for a missing file (ie a URL with a dot in it)
app.all('/*\.*', function (req, res) {
res.status(404).send('404 Not found');
});
// This route deals enables HTML5Mode by forwarding "missing" links to the index.html
app.all('/**', function (req, res) {
req.url = 'index.html';
app.handle(req, res);
});
Using the next function does not work if the next handler is not added in the right order. Instead of using next, I use the router to register the handlers and call
router.get("/a/path", function(req, res){
req.url = "/another/path";
router.handle(req, res);
}
Express 4+ with nested routers
Instead of having to use the outside of route/function app, you can use req.app.handle
"use strict";
const express = require("express");
const app = express();
//
// Nested Router 1
//
const routerOne = express.Router();
// /one/base
routerOne.get("/base", function (req, res, next) {
res.send("/one/base");
});
// This routes to same router (uses same req.baseUrl)
// /one/redirect-within-router -> /one/base
routerOne.get("/redirect-within-router", function (req, res, next) {
req.url = "/base";
next();
});
// This routes to same router (uses same req.baseUrl)
// /one/redirect-not-found -> /one/two/base (404: Not Found)
routerOne.get("/redirect-not-found", function (req, res, next) {
req.url = "/two/base";
next();
});
// Using the full URL
// /one/redirect-within-app -> /two/base
routerOne.get("/redirect-within-app", function (req, res, next) {
req.url = "/two/base";
// same as req.url = "/one/base";
//req.url = req.baseUrl + "/base";
req.app.handle(req, res);
});
// Using the full URL
// /one/redirect-app-base -> /base
routerOne.get("/redirect-app-base", function (req, res, next) {
req.url = "/base";
req.app.handle(req, res);
});
//
// Nested Router 2
//
const routerTwo = express.Router();
// /two/base
routerTwo.get("/base", function (req, res, next) {
res.send("/two/base");
});
// /base
app.get("/base", function (req, res, next) {
res.send("/base");
});
//
// Mount Routers
//
app.use("/one/", routerOne);
app.use("/two/", routerTwo);
// 404: Not found
app.all("*", function (req, res, next) {
res.status(404).send("404: Not Found");
});
app.get('/menzi', function (req, res, next) {
console.log('menzi2');
req.url = '/menzi/html/menzi.html';
// res.redirect('/menzi/html/menzi.html');
next();
});
This is my code:when user enter "/menzi",the server will give the page /menzi/html/menzi.html to user, but the url in the browser will not change;
You can use run-middleware module exactly for that. Just run the handler you want by using the URL & method & data.
https://www.npmjs.com/package/run-middleware
For example:
app.runMiddleware('/get-user/20',function(code,body,headers){
res.status(code).send(body)
})