How to disable webpage caching in ExpressJS + NodeJS? - node.js

By default, my browser caches webpages of my ExpressJS app.
This is causing a problem to my login system (users not logged in can open old cached pages of logged in users).
How do I disable this caching?
EDIT:
My app.js (main file):
var express = require('express');
var http = require('http');
var path = require('path');
var store = require('./routes/store');
var app = express();
app.configure(function(){
app.set('port', process.env.PORT || 3012);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser('your secret here'));
app.use(express.session());
app.use(app.router);
app.use(require('stylus').middleware(__dirname + '/public'));
app.use(express.static(path.join(__dirname, 'public')));
});
app.configure('development', function(){
app.use(express.errorHandler());
});
app.get('/', store.home);
app.post('/', store.home);
app.get('/addProblem', store.addProblem);
app.post('/addProblem', store.addProblem);
app.get('/problem', store.problem);
app.post('/problem', store.problem);
app.get('/problemList', store.problemList);
app.post('/problemList', store.problemList);
app.get('/main', store.main);
app.post('/main', store.main);
app.post('/login', store.login);
app.get('/login', store.login);
app.get('/createProblem', store.createProblem);
app.post('/createProblem', store.createProblem);
app.post('/register', store.register);
app.get('/register', store.register);
app.post('/evaluate', store.evaluate);
app.get('/evaluate', store.evaluate);
app.get('/logout', store.logout);
app.post('/logout', store.logout);
http.createServer(app).listen(app.get('port'), function(){
console.log("Express server listening on port " + app.get('port'));
});

There are two things to consider when dealing with cache in Express.js - ETag and Cache-Control headers.
ETag (MDN reference)
If you have dynamic content which does not benefit from ETags, it's best to disable it because it incurs small overhead with each request.
app.set('etag', false)
Cache-Control (MDN reference)
To completely disable cache, use the following header:
app.use((req, res, next) => {
res.set('Cache-Control', 'no-store')
next()
})
This header does not affect express.static() middleware. It handles cache in its own way.

nocache
Don't waste your time reinventing the wheel, use the nocache middleware instead. It has been here for 8 years (2023) and it is downloaded more than 1.5 million times per week. Having only 100 stars on github, this is actually one of those unsung heroes of the express ecosystem.
Install it
npm install --save nocache
Add it to you app:
const nocache = require('nocache');
app.use(nocache());
When installed as a middleware it sets four headers, disabling a lot of browser caching. This is the complete list of the updated headers.
Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate
Pragma: no-cache
Expires: 0
Surrogate-Control: no-store
Beware of ETag
Even if you are using nocache, the ETag header isn't removed, because it works in a different way. It's generated at the end of the request and could be another source of unintended caching. In order to handle it you have two choices.
app.set
The first is disabling it using express builtin app.set('etag', false); method.
on-headers
The second is removing the header just before it is sent to the client using the on-headers module:
const onHeaders = require('on-headers');
// install it as a middleware
app.use((req, res, next) => {
// listen for the headers event
onHeaders(res, () => {
this.removeHeader('ETag');
});
});
As pointed out in the comments this is actually a "ten-liner" package, but do you really want to copy and paste the same block of code on every express project? Or worse, publish another similar package? I don't think so and I'm sure your colleagues think the same too ;-)

app.disable('view cache');
^^ Code for ExpressJS

You can create a middleware, set headers in it so that there is no caching, and use in those route handlers that require authorization.
middleware cookies:
const cookie = async (req, res, next) => {
try {
const token = req.cookies.token;
const check = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findOne({_id: check._id, 'tokens.token': token});
if (!user) {
throw new Error();
}
req.token = token;
req.user = user;
res.set({
"Cache-Control": "no-store, no-cache, must-revalidate, proxy-revalidate",
"Pragma": "no-cache",
"Expires": "0",
"Surrogate-Control": "no-store"
});
next();
} catch (e) {
res.set({
"Cache-Control": "no-store, no-cache, must-revalidate, proxy-revalidate",
"Pragma": "no-cache",
"Expires": "0",
"Surrogate-Control": "no-store"
}).redirect(301, '/login');
}
};
used:
router.get('/all/tasks', cookie, async (req, res) => {
try {
const task = await Task.find({owner: req.user._id});
if (!task) {
return res.status(404).send();
}
res.status(200).render('tasks', {
title: 'Your task',
task: task
});
} catch {
res.status(500).send();
}
});
Ps: I took the headers from the nocashe library https://www.npmjs.com/package/nocache

Related

Parse XML body from HTTP Push request with Express & Node.js, using body-parser-xml

I need to process a HTTP push request using Node.js express.
The request is sending the body in XML format, that's why I chose the body-parser-xml package for parsing.
My problem is, that the body isn't properly parsed – I guess because the package doesn't recognize the mime type of the transferred body.
The endpoint:
const express = require('express');
const bodyParser = require('body-parser');
require('body-parser-xml')(bodyParser);
const app = express();
const PORT = 8085;
app.use(express.urlencoded({ extended: true }));
app.use(bodyParser.xml({
limit:'25MB'
}));
app.post('/feed', function (req, res, body) {
console.log(req.headers);
console.log(req.body);
res.status(200).end();
});
The output:
{
host: 'localhost:8085',
accept: '*/*',
'x-meta-feed-type': '1',
'x-meta-feed-parameters': 'feed params',
'x-meta-default-filename': 'filename.xml',
'x-meta-mime-type': 'text/xml',
'content-length': '63'
encoding: 'UTF-8',
connection: 'Keep-Alive'
}
{
'<data id': '"1234"><name>Test</name><title>Test1234</title></data>'
}
I'm not able to change the request itself (it's external), only the Node.js endpoint.
Any idea how to process the content properly?
Thanks for your help!
The request has apparently been parsed by the
app.use(express.urlencoded({ extended: true }));
middleware, which means that it must have had Content-Type: application/x-www-form-urlencoded. Remove the two app.use lines, because they make "global" body-parsing decisions (for every request), whereas you need a special treatment only for one type of request.
If you instantiate the XML body parser with the "non-standard" (that is, wrong) type, it will parse the content as XML:
app.post('/feed',
bodyParser.xml({type: "application/x-www-form-urlencoded"}),
function (req, res) {
console.log(req.headers);
console.log(req.body);
res.status(200).end();
});

Preventing posts from origin other than domain with axios

I'm working on my first website, and am using axios to send post/get requests to the backend. I'm using React on the front-end and node/express on the back-end. I'm wondering if there is a way to prevent posts from a source other than my site.
For example, if I make this exact request through postman I am still be able to post comments, meaning that someone could post with names and ID's other than themselves.
Here is a typical post request made on the front-end:
axios.post('/api/forumActions/postComment', {}, {
params: {
postUserID: this.props.auth.user.id,
name: `${this.props.auth.user.firstName} ${this.props.auth.user.lastName}`,
commentContent: this.state.commentContent,
respondingToPost: this.state.postID,
respondingToComment: this.state.postID
}
})
And here is how it gets processed on the back-end
app.use(
bodyParser.urlencoded({
extended: false
})
);
app.use(bodyParser.json());
app.use(passport.initialize());
require("./config/passport")(passport);
app.post('/postComment', (req, res)=>{
var commentData={
postUserID: req.query.postUserID,
name: req.query.name,
commentContent: req.query.commentContent,
respondingToPost: req.query.respondingToPost,
respondingToComment: req.query,respondingToComment
}
//Write commentData to database
})
const port = process.env.PORT || 80;
const server = app.listen(port, () => console.log(`Server running on port ${port} !`));
I'm wondering if there is anything I can do to ramp up security to prevent post requests being made from anywhere?
You can use cors to accomplish this. This is a pretty good guide on how to configure it, specifically this section. You can configure it for certain routes, or all across the board.
CORS sets the Access-Control-Allow-Origin header, which you can read more about here - it only allows requests from specified origins.
Keep in mind you don't need that package to accomplish this.. you could always build your own middleware for this.
Something like:
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "http://yourdomain.com");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
Within the Express documentation, they provide the following demo code, which you should be able to use as a helper.
Client
Server
You could use a makeshift middleware with special headers.. but then all someone has to do is read your client side source code, or look at the network tab in their browser to figure out which headers you're sending, so then can duplicate them. It would prevent random people from snooping, though..
const express = require('express');
const path = require('path');
const app = express();
const port = process.env.PORT || 3000;
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Custom special middleware..
function blockBadHosts({ host, whitelistHeader, whitelistHeaderValue }) {
return (req, res, next) => {
if(req.headers['host'] === host) {
if(whitelistHeader && req.headers[whitelistHeader] === whitelistHeaderValue) {
next();
} else {
res.status(301).send('BAD REQUEST');
}
} else {
res.status(301).send("BAD REQUEST");
}
}
}
// Options for our custom middleware
const badHostOptions = {
host: "localhost:3000",
whitelistHeader: "x-my-special-header", // Request must contain this header..
whitelistHeaderValue: "zoo" // .. with this value
}
// This should succeed
app.get('/success', (req, res) => {
res.status(200).send("from /success");
});
// This should fail even if sent from Postman without correct headers
app.get('/failure', blockBadHosts(badHostOptions), (req, res) => {
res.status(200).send("from /failure");
});
// 404 route
app.use((req, res) => {
res.status(404).send("Uh oh can't find that");
})
app.listen(port, () => {
console.log(`App listening on port: '${port}'`);
});

How can I add CORS-Headers to a static connect server?

I am writing a little demo web server delivering static html,css and javascript.
The server looks like
(function () {
"use strict";
var http = require("http");
var connect = require('connect');
var app = connect()
.use(connect.logger('dev'))
.use(connect.static('home'));
var server = http.createServer(app);
server.listen(9999, function () {
console.log('server is listening');
});
})();
My client side javascript makes ajax calls to a different server.
How can I add
Access-Control-Allow-Origin: http://example.com
to my server response, so that the client side javascript can do the ajax call?
Had a bit of trouble figuring this one out since express has spoiled me.
Take a look at enable cors. Basically what you need to be doing is add Access-Control-Allow-Origin to the domain you want to enable cors on. response.setHeaders is perfect for this task.
Another thing to note is that connect has no way to handle routes. If your app needs to have different routes then you will probably have to write logic for each of them and add res headers to the ones on which you want to enable cors. You can use req.url for it.
var http = require("http");
var connect = require('connect');
var app = connect()
.use(connect.logger('dev'))
.use(connect.static('home'))
.use(function(req, res){
res.setHeader("Access-Control-Allow-Origin", "http://example.com");
res.end('hello world\n');
});
var server = http.createServer(app);
server.listen(9999, function () {
console.log('server is listening');
});
This is the response I got in chrome dev tools
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://example.com
Date: Sat, 15 Jun 2013 16:01:59 GMT
Connection: keep-alive
Transfer-Encoding: chunked
I hope this will help:
//CORS middleware
var allowCrossDomain = function(req, res, next) {
res.header('Access-Control-Allow-Origin', config.allowedDomains);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type');
next();
}
//...
app.configure(function() {
app.use(allowCrossDomain);
app.use(express.static(__dirname + '/public'));
});
More detail:
How to allow CORS?
express.static takes a configuration object. You can provide the property setHeaders and from that function you can set headers for the response:
app.use(express.static('public', {
setHeaders: function setHeaders(res, path, stat) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET');
res.header('Access-Control-Allow-Headers', 'Content-Type');
}
}))
The parameters to this function are:
res, the response object.
path, the file path that is being sent.
stat, the stat object of the file that is being sent.
I wish that the request were available here so that I could conditionally set the CORS headers based upon the Origin header.
The easiest method, if you are using gulp would be to use gulp plugin called "gulp-connect" and "connect-modrewrite" and define a new gulptask to redirect a particular api.
This is to make the apache act as a Proxy for the particular api, to bypass the pre-flight request, inorder to avoid CORs issue.
I used the following gulp task to overcome this problem.
var connect = require('gulp-connect'),
modRewrite = require('connect-modrewrite');
/**
* Proxy Config
*/
gulp.task('connect', function () {
connect.server({
root: ['./.tmp', './.tmp/{folderLocations}', './src', './bower_components'],
port: 9000,
livereload: true,
middleware: function (connect, opt) {
return [
modRewrite([
'^/loginProxy/(.*)$ http://app.xyzdomain.com/service/login/auth/$1 [P]'
])
];
}
});
});

How can I set cookie in node js using express framework?

In my application, I need to set a cookie using the express framework. I have tried the following code but it's not setting the cookie.
var express = require('express'), http = require('http');
var app = express();
app.configure(function(){
app.use(express.cookieParser());
app.use(express.static(__dirname + '/public'));
app.use(function (req, res) {
var randomNumber=Math.random().toString();
randomNumber=randomNumber.substring(2,randomNumber.length);
res.cookie('cokkieName',randomNumber, { maxAge: 900000, httpOnly: true })
console.log('cookie have created successfully');
});
});
var server = http.createServer(app);
var io = require('socket.io').listen(server);
server.listen(5555);
The order in which you use middleware in Express matters: middleware declared earlier will get called first, and if it can handle a request, any middleware declared later will not get called.
If express.static is handling the request, you need to move your middleware up:
// need cookieParser middleware before we can do anything with cookies
app.use(express.cookieParser());
// set a cookie
app.use(function (req, res, next) {
// check if client sent cookie
var cookie = req.cookies.cookieName;
if (cookie === undefined) {
// no: set a new cookie
var randomNumber=Math.random().toString();
randomNumber=randomNumber.substring(2,randomNumber.length);
res.cookie('cookieName',randomNumber, { maxAge: 900000, httpOnly: true });
console.log('cookie created successfully');
} else {
// yes, cookie was already present
console.log('cookie exists', cookie);
}
next(); // <-- important!
});
// let static middleware do its job
app.use(express.static(__dirname + '/public'));
Also, middleware needs to either end a request (by sending back a response), or pass the request to the next middleware. In this case, I've done the latter by calling next() when the cookie has been set.
Update
As of now the cookie parser is a seperate npm package, so instead of using
app.use(express.cookieParser());
you need to install it separately using npm i cookie-parser and then use it as:
const cookieParser = require('cookie-parser');
app.use(cookieParser());
Set Cookie?
res.cookie('cookieName', 'cookieValue')
Read Cookie?
req.cookies
Demo
const express('express')
, cookieParser = require('cookie-parser'); // in order to read cookie sent from client
app.get('/', (req,res)=>{
// read cookies
console.log(req.cookies)
let options = {
maxAge: 1000 * 60 * 15, // would expire after 15 minutes
httpOnly: true, // The cookie only accessible by the web server
signed: true // Indicates if the cookie should be signed
}
// Set cookie
res.cookie('cookieName', 'cookieValue', options) // options is optional
res.send('')
})
Not exactly answering your question, but I came across your question, while looking for an answer to an issue that I had. Maybe it will help somebody else.
My issue was that cookies were set in server response, but were not saved by the browser.
The server response came back with cookies set:
Set-Cookie:my_cookie=HelloWorld; Path=/; Expires=Wed, 15 Mar 2017 15:59:59 GMT
This is how I solved it.
I used fetch in the client-side code. If you do not specify credentials: 'include' in the fetch options, cookies are neither sent to server nor saved by the browser, even though the server response sets cookies.
Example:
var headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Accept', 'application/json');
return fetch('/your/server_endpoint', {
method: 'POST',
mode: 'same-origin',
redirect: 'follow',
credentials: 'include', // Don't forget to specify this if you need cookies
headers: headers,
body: JSON.stringify({
first_name: 'John',
last_name: 'Doe'
})
})
Set a cookie:
res.cookie('cookie', 'monster')
https://expressjs.com/en/4x/api.html#res.cookie
Read a cookie:
(using cookie-parser middleware)
req.cookies['cookie']
https://expressjs.com/en/4x/api.html#req.cookies
Setting cookie in the express is easy
first install cookie-parser
npm install cookie-parser
using middleware
const cookieParser = require('cookie-parser');
app.use(cookieParser());
Set cookie know more
res.cookie('cookieName', '1', { expires: new Date(Date.now() + 900000), httpOnly: true })
Accessing that cookie know more
console.dir(req.cookies.cookieName)
Done!
setting a cookie can be done as such:
res.cookie('cookie name', 'cookie value', [options])
where cookie_name is the name(String) of the cookie you wish to set, for example - "token", and the cookie value is the value(String) you wish to store in the said cookie.
as far as options go, you can read more about them here:
https://expressjs.com/en/api.html
one example of an option is 'maxAge' which indicates how long a cookie is valid, this is used for example when assigning an authentication token and you wish to limit the time a user can stay logged in before having to re-login.
Reading a cookie can be done as such:
req.cookies['cookie name']
which will return the value of the cookie.
Isomorphic Read cookie helper:
function getCookieValue(cookieName = '', cookie = '') {
const matches = cookie.match(`(^|[^;]+)\\s*${cookieName}\\s*=\\s*([^;]+)`)
return matches ? matches.pop() : ''
}
// Node with express:
getCookieValue('cookieName', req.headers.cookie)
// Browser:
getCookieValue('cookieName', document.cookie)
Write in Node with express:
res.cookie('cookieName', 'cookieValue')
Write in the browser:
function setCookie(
cname,
cvalue,
exdays = 100 * 365 /* 100 days */
) {
const now = new Date()
const expireMs = exdays * 24 * 60 * 60 * 1000
now.setTime(now.getTime() + expireMs)
document.cookie = `${cname}=${cvalue};expires=${now.toUTCString()};path=/`
}
// Example of usage
setCookie('cookieName', 'cookieValue')
If you have a problem with setting multiple cookies for one request
Try this way:
res.setHeader('Set-Cookie', [
`accessToken=${accessToken}; HttpOnly; Path=/; Max-Age=${60 * 60}; Secure=True;`,
`refreshToken=${refreshToken}; HttpOnly; Path=/; Max-Age=${60 * 60 * 24 * 7 * 2}; Secure=True;`
]);

Handling text/plain in Express (via connect)?

I am using Express 3, and would like to handle text/plain POSTs.
Express 3 uses connect's bodyParser now (I think the old Express code got moved to connect). The documentation for bodyParser gives some details about how to make it support additional file types. And I found an excellent blog post about how handling text/plain was done in old versions of Express).
Should I explicitly require connect (and let node's require cache the modified version)? Or is connect exposed via express somewhere?
connect.bodyParser does not have a 'parse' key.
How can I make Express (via connect) handle text/plain POSTs?
With bodyParser as dependency, add this to your app.js file.
var bodyParser = require('body-parser');
var app = express();
app.use(bodyParser.text());
Happy Noding.
https://gist.github.com/3750227
app.use(function(req, res, next){
if (req.is('text/*')) {
req.text = '';
req.setEncoding('utf8');
req.on('data', function(chunk){ req.text += chunk });
req.on('end', next);
} else {
next();
}
});
Will add the text as req.text
In express.js "^4.16..." the following code works fine for me:
// parse an HTML body as a string
app.use(bodyParser.text({ type: 'text/*' }))
The extended piece of the code is below:
// parse an HTML body as a string
app.use(bodyParser.text({ type: 'text/*' }))
// Enable CORS for ExpressJS
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS')
res.header('Access-Control-Allow-Credentials', true)
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Methods, Credentials')
next()
})
// Api url
app.post('/api/myApi', (req, res) => {
const bodyJson = JSON.parse(req.body)
// do something
}
I would just make a module similar to the json.js middleware module and just don't bother converting the buf data into anything else. Wrap it into a plain.js file, apply some decent "don't repeat yourself" refactoring, and submit a pull request to connect. Seems generally handy. However, note that while convenient, large enough request bodies will require streaming straight to disk at some point so you don't consume all the memory in your node server.
You may try this :
var expressApi= (req, res,params)=>{
console.log('req.body',params);
var body = '';
req.on('data', function (data) {
body += data;
});
req.on('end', function () {
res.write({status:200,message:'read data'});
});
}
You can parse every type to json via set type option
app.use(express.json({ type: ['text/*', '*/json'] }))

Resources