0

I'm trying to build a Node/Express server that essentially acts as a middleman server and logs any requests that come in, then forwards the request to the destination server, and forwards back any responses that come from the destination server. The goal is to be as "transparent as possible, making it seem as if there is no middleman server at all.

The problem I'm having is that my express server seems to be dumping in a bunch of unnecessary headers in the response from the destination server.

In my app.js I have some (a lot of) middlewares that are useful for my app in general but seems to inject headers in the response:

app.use(rateLimiter);
app.use(helmet());
app.use(xss());
app.use(mongoSanitize());
app.use(nocache());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());

Then I have my middleware endpoint that receives the request, forwards it, and returns the response:

exports.createLink = async (req, res, next) => {
  try {
    const url = 'https://destination-endpoint.com';
    const options = {
      url: url,
     };
 
    request( options, function(err, remoteResponse, remoteBody) {
      res.writeHead(remoteResponse.statusCode, {...remoteResponse.headers});
      return res.end(remoteBody);
    });

  } catch (error) {
    console.log(error);
    next(error);
  }
};

If I hit my middleman I get the response:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:3000
Vary: Accept-Encoding
Access-Control-Allow-Credentials: true
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
date: Sun, 24 Jan 2021 05:23:07 GMT
X-RateLimit-Reset: 1611465796
Content-Security-Policy: default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests
X-DNS-Prefetch-Control: off
Expect-CT: max-age=0
X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Download-Options: noopen
X-Content-Type-Options: nosniff
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: no-referrer
X-XSS-Protection: 0
Surrogate-Control: no-store
cache-control: no-cache, private
Pragma: no-cache
Expires: 0
server: nginx/1.14.2
content-type: text/plain; charset=UTF-8
transfer-encoding: chunked
connection: close
x-request-id: 864b8443-5fe2-498e-8e88-662035afe6c7
x-token-id: d0cb94e2-9c87-4d6e-b32a-11fcc698ad2c
set-cookie: laravel_session=tBlSCeel0OFIR5pL9C6f02JfXGqoyg3SN6BH6jjG; expires=Sun, 24-Jan-2021 07:23:07 GMT; Max-Age=7200; path=/; httponly

{
"foo": "bar"
}%         

While if I hit the endpoint directly I just get this:

HTTP/1.1 200 OK
Server: nginx/1.14.2
Content-Type: text/plain; charset=UTF-8
Transfer-Encoding: chunked
Vary: Accept-Encoding
X-Request-Id: 1f033b57-4a2f-48a1-82b3-41bce6e2d748
X-Token-Id: d0cb94e2-9c87-4d6e-b32a-11fcc698ad2c
Cache-Control: no-cache, private
Date: Sun, 24 Jan 2021 05:24:28 GMT
Set-Cookie: laravel_session=MdOGJ18iDU28XLmNYXf5T2RxSa25KxqFisxzCBzR; expires=Sun, 24-Jan-2021 07:24:28 GMT; Max-Age=7200; path=/; httponly

{
"foo": "bar"
}%    

As you can see there is a lot of extra stuff added to the header. Some of it may be solvable by removing middlewares/etc. but I think the bigger issue is that I want to return the response from the destination server AS IS no matter what. I found that I can delete the current headers before sending it like this:

   request( options, function(err, remoteResponse, remoteBody) {
        const headers = res.getHeaders();
        for (const head in headers){
          res.removeHeader(head);
        }
        res.writeHead(remoteResponse.statusCode, {...remoteResponse.headers});
        return res.end(remoteBody);
    });

But that seems very heavy handed, there has to be an easier way to overwrite/set all response headers to exactly what I need. So my overall question is: How do I return the response from the request to the destination server EXACTLY as is?

DasBeasto
  • 2,082
  • 5
  • 25
  • 65
  • because helmet do this – 王仁宏 Jan 24 '21 at 05:33
  • 1
    That is helmet do to let your web app more security in modern browser. – 王仁宏 Jan 24 '21 at 05:36
  • Just see its doc, you will find out. – 王仁宏 Jan 24 '21 at 05:36
  • I wonder if you get a different result if you just `pipe` the input back to the client which is more typical way to forward a response from one host back to your client. And, then remove all the safety middleware, see what headers have been added, then add that middleware back one at a time to see who's adding what, then examine it's configuration options to see what you can customize. As it is, your question is a bit odd because you've added a lot of middleware that adds custom headers and then you ask why you have so many custom headers being added. – jfriend00 Jan 24 '21 at 06:23
  • Or, you could put all the middleware after this request handler so it is not in force for this particular request. – jfriend00 Jan 24 '21 at 06:26
  • @王仁宏 removing helmet didn't seem to actually change the headers at all as far as I can tell. It still has all of the extraneous headers added. – DasBeasto Jan 24 '21 at 06:43
  • @jfriend00 I tried putting it before all the middlewares and that removed all the extra headers except for "X-Powered-By: Express", but that seems more like a workaround then a solution because I cant even add back in my CORS middleware without some extra headers coming back which CORS is necessary for this route (and the other middlewares like sanitize are preferred). I'll experiment with pipe and see what that does for me. – DasBeasto Jan 24 '21 at 06:46
  • obviously, it not only helmet but also other middleware like nocache. nocache add `Pragma` and `no-cache` header to response. – 王仁宏 Jan 24 '21 at 06:54
  • @DasBeasto - OK, now you know where all the headers are coming from. They are coming from the tools you added to your project. And, to do it's job, CORS support has to add custom headers - that's how it works. If you're accessing this route via Javascript from a cross origin in a browser, you will need those headers - no way around that. If you're not accessing the route via Javascript in the browser, you don't need the CORS headers for a top level URL typed into the browser bar. – jfriend00 Jan 24 '21 at 06:55
  • @jfriend00 he is trying to proxy a website – 王仁宏 Jan 24 '21 at 06:56
  • @DasBeasto - Since all the headers disappeared when you removed all the middleware, `.pipe()` is probably not going to change things. It is a more efficient way to forward a response back to your client anyway, so I would recommend using it, but I don't think it will change anything related to the headers. The `X-Powered-By` header can be removed in Express. – jfriend00 Jan 24 '21 at 06:57
  • @王仁宏 - I understand, but he says he needs CORs, so he must be accessing this route from Javascript in the browser, not from a top level URL typed into the URL bar. Obviously, we could help better if we could see the code for the whole route and see an explanation of how it is being used from the browser. – jfriend00 Jan 24 '21 at 06:58
  • You can remove the `X-Powered-By` header as described [here](https://stackoverflow.com/a/5867710/816620). Note, you can either remove it globally or for just specific routes. – jfriend00 Jan 24 '21 at 07:00

0 Answers0