http-proxy-middleware icon indicating copy to clipboard operation
http-proxy-middleware copied to clipboard

Api behind Basic authentication and NTLM authentication

Open zychj1 opened this issue 9 years ago • 82 comments

Hi, I have api on iis server behind basic windows authentication and i cannot use cors. So I tried to use this module but however I configure it I cannot log into api and I get 401 every time

I tried

    server.middleware = proxyMiddleware(
        '/api',
        {
            target: 'API_HOST',
            logLevel: 'debug'
        }
    );
    server.middleware = proxyMiddleware(
        '/api',
        {
            target: 'API_HOST',
            logLevel: 'debug',
            auth: 'LOGIN:PASS'
        }
    );
    server.middleware = proxyMiddleware(
        '/api',
        {
            target: 'http://LOGIN:PASS@API_HOST',
            logLevel: 'debug'
        }
    );

zychj1 avatar Nov 20 '15 08:11 zychj1

Hi, I haven't used the auth option personally yet.

the auth option is provided by the underlying http-proxy library. Did a search in their https://github.com/nodejitsu/node-http-proxy/tree/master/examples, but I could not find any examples how to use the auth option.

But I did found a unit test: https://github.com/nodejitsu/node-http-proxy/blob/302d981dd2cf06dbf751b1f64e3dfea08d0f9476/test/lib-http-proxy-passes-web-incoming-test.js#L303

Their unit test looks like the 2nd case you've provided.

Is 'API_HOST' really your target? the target should include the http: or https: protocol

chimurai avatar Nov 20 '15 10:11 chimurai

You could try setting changeOrigin to true and see if that helps.

server.middleware = proxyMiddleware(
  '/api',
  {
    target: 'API_HOST',
    logLevel: 'debug',
    auth: 'LOGIN:PASS',
    changeOrigin: true
  }
);

chimurai avatar Nov 20 '15 10:11 chimurai

Yep API HOST is really my target, when i go to localhost:3000/api uri directly in the browser i get 401 error page and requests are forwarded to correct url but i cannot authorize. i get something in header like:

 connection:close
 content-type:text/html
 date:Wed, 25 Nov 2015 06:53:58 GMT
 server:Microsoft-IIS/7.5
 Transfer-Encoding:chunked
 www-authenticate:Negotiate, NTLM
 x-powered-by:ASP.NET

from browse ajax request:

 access-control-allow-credentials:true
 access-control-allow-headers:Origin,X-Requested-With,Content-Type,Accept
 access-control-allow-methods:GET,HEAD,POST,PUT,DELETE,OPTIONS
 access-control-allow-origin:http://localhost:3000
 connection:close
 content-length:1293
 content-type:text/html
 date:Wed, 25 Nov 2015 07:00:03 GMT
 server:Microsoft-IIS/7.5
 www-authenticate:Negotiate, NTLM
 x-powered-by:ASP.NET

zychj1 avatar Nov 25 '15 07:11 zychj1

http-proxy-middleware uses http-proxy to do the actual proxy work.

This is what I found in their NTLM authentication example: https://github.com/nodejitsu/node-http-proxy/blob/master/examples/http/ntlm-authentication.js

You'll have to modify the headers. The http-proxy proxyRes event is exposed via the http-proxy-middlware option: onProxyRes. Example: https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/onProxyRes.md

If I combine the two examples you'll get this config:

var proxyMiddleware = require("http-proxy-middleware");

var onProxyRes = function (proxyRes, req, res) {
     var key = 'www-authenticate';
     proxyRes.headers[key] = proxyRes.headers[key] && proxyRes.headers[key].split(',');
};

var options = {
    target: 'API_HOST',
    logLevel: 'debug',
    auth: 'LOGIN:PASS',
    onProxyRes: onProxyRes
};

var proxy = proxyMiddleware('/api', options);

Let me know if this works.

If it doesn't, you might want to ask the question at: https://github.com/nodejitsu/node-http-proxy/issues; since the authentication part is handled by the http-proxy library.

chimurai avatar Nov 25 '15 21:11 chimurai

I can see some progress for

    var onProxyRes = function (proxyRes, req, res) {
        var key = 'www-authenticate';
        proxyRes.headers[key] = proxyRes.headers[key] && proxyRes.headers[key].split(',');
    };

    server.middleware = proxyMiddleware(
        '/api',
        {
            target: 'API_HOST',
            logLevel: 'debug',
            auth: 'login:pass',
            onProxyRes: onProxyRes
        }
    );

right now on going on localhost:3000/api is infinite authorization popup, i will ask on the other project as You suggest

zychj1 avatar Nov 26 '15 06:11 zychj1

That sounds promising.

I noticed the usage of agentkeepalive in their example;

Did a search on the nuts and bolts of the NTLM Authentication Scheme: ~http://www.innovation.ch/personal/ronald/ntlm.html~ https://web.archive.org/web/20210126065105/http://www.innovation.ch/personal/ronald/ntlm.html

NTLM Authentication Scheme for HTTP

Keeping the connection alive

This scheme authenticates connections, not requests. This manifests itself in that the network connection must be kept alive during the second part of the handshake

This explains why 'keep alive' is needed.

Updated configuration:

var Agent = require('agentkeepalive');
var proxyMiddleware = require("http-proxy-middleware");

var keepaliveAgent =  new Agent({
    maxSockets: 100,
    keepAlive: true,
    maxFreeSockets: 10,
    keepAliveMsecs:1000,
    timeout: 60000,
    keepAliveTimeout: 30000 // free socket keepalive for 30 seconds
});

var onProxyRes = function (proxyRes, req, res) {
     var key = 'www-authenticate';
     proxyRes.headers[key] = proxyRes.headers[key] && proxyRes.headers[key].split(',');
};

var options = {
    target: 'API_HOST',
    logLevel: 'debug',
    auth: 'LOGIN:PASS',
    agent: keepaliveAgent,
    onProxyRes: onProxyRes
};

var proxy = proxyMiddleware('/api', options);

chimurai avatar Nov 26 '15 08:11 chimurai

@zychj1 any progress on this issue?

chimurai avatar Dec 04 '15 14:12 chimurai

@chimurai I run in exactly the same problem and I can confirm that the configuration provided in your comment from Nov 26, 2015 (with keepalive and header rewriting) is solving the issue. Many thanks!

pierreconstantial avatar Apr 25 '16 08:04 pierreconstantial

@pierreconstantial Glad to hear the example NTLM configuration solves the issue. Thanks for the confirmation!

chimurai avatar Apr 25 '16 10:04 chimurai

hey @chimurai i'm having the same problem discussed on nodejs/node#8004, I've tried to use the same configuration as here and also use the simple Agent with keepAlive option of node's http module.

I'm using webpack-dev-server with the proxy property and having a WebApi server with NTLM authentication.

Thanks ahead for your help!

EladBezalel avatar Nov 14 '16 14:11 EladBezalel

@EladBezalel can you provide a minimal setup in which the problem is exposed?

chimurai avatar Nov 14 '16 14:11 chimurai

In my webpack.js:

{
  devServer: {
    '/api/*': {
      target: 'http://localhost:12121',
      logLevel: 'debug',
      agent: new Agent({
        maxSockets: 100,
        keepAlive: true,
        maxFreeSockets: 10,
        keepAliveMsecs:1000,
        timeout: 60000,
        keepAliveTimeout: 30000 // free socket keepalive for 30 seconds
     }),
     onProxyRes: proxyRes => {
        var key = 'www-authenticate';
        proxyRes.headers[key] = proxyRes.headers[key] && proxyRes.headers[key].split(',');
      }
    }
  }
}

EladBezalel avatar Nov 14 '16 14:11 EladBezalel

Are you using agentkeepalive as Agent? (example)

var Agent = require('agentkeepalive');

chimurai avatar Nov 14 '16 15:11 chimurai

Yes I am

EladBezalel avatar Nov 14 '16 15:11 EladBezalel

Not sure what causes the issue; Kinda turns into a guessing game... Can you provide a minimal working example?

chimurai avatar Nov 14 '16 16:11 chimurai

I'll try to have it tomorrow, I also tried it without webpack-dev-server just simple express and the middleware and got the same error..

EladBezalel avatar Nov 14 '16 16:11 EladBezalel

Here it is: https://ufile.io/c3215 Three zips in there -

  1. proxy - the express server using http-proxy-middleware
  2. owin - source code of the c# console application using Owin that answers any request with status 200
  3. Debug - the exe of the owin server (listening in port 12345)

Run the owin server exe, and on your browser go to http://localhost:12345/api/whatever and see that you get a page with {"status":"ok"} in it, now go to http://localhost:3000/api/whatever and see how the proxy server dies with the same error from above..

thanks!

EladBezalel avatar Nov 16 '16 14:11 EladBezalel

Thanks for the effort! This is what I see when I turn on the node debug logging:

$ NODE_DEBUG=http,net node index.js

[HPM] Proxy created: /  ->  http://localhost:12345
[HPM] Subscribed to http-proxy events:  [ 'proxyRes', 'error', 'close' ]
NET 4776: listen2 null 3000 4 false undefined
NET 4776: _listen2: create a handle
NET 4776: bind to ::
NET 4776: onconnection
NET 4776: _read
NET 4776: Socket._read readStart
HTTP 4776: SERVER new http connection
[HPM] GET /api/whatever -> http://localhost:12345
NET 4776: createConnection [ { keepAliveTimeout: 30000,
    timeout: 60000,
    keepAliveMsecs: 1000,
    maxFreeSockets: 10,
    keepAlive: true,
    maxSockets: 100,
    path: null,
    localAddress: undefined,
    agent:
     Agent {
       domain: null,
       _events: [Object],
       _eventsCount: 4,
       _maxListeners: undefined,
       defaultPort: 80,
       protocol: 'http:',
       options: [Object],
       requests: {},
       sockets: [Object],
       freeSockets: {},
       keepAliveMsecs: 1000,
       keepAlive: true,
       keepAliveTimeout: 30000,
       timeout: 60000,
       maxSockets: 100,
       maxFreeSockets: 10,
       createSocketCount: 0,
       closeSocketCount: 0,
       errorSocketCount: 0,
       requestCount: 0,
       timeoutSocketCount: 0 },
    headers:
     { 'accept-language': 'en-US,en;q=0.8,nl;q=0.6',
       'accept-encoding': 'gzip, deflate, sdch',
       dnt: '1',
       accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
       'user-agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36',
       'upgrade-insecure-requests': '1',
       'cache-control': 'max-age=0',
       connection: 'keep-alive',
       host: 'localhost:3000' },
    method: 'GET',
    secureProtocol: undefined,
    ciphers: undefined,
    ca: undefined,
    cert: undefined,
    passphrase: undefined,
    key: undefined,
    pfx: undefined,
    socketPath: undefined,
    hostname: 'localhost',
    host: 'localhost',
    port: '12345',
    servername: 'localhost',
    encoding: null } ]
NET 4776: pipe false null
NET 4776: connect: find host localhost
NET 4776: connect: dns options { family: undefined, hints: 1024 }
HTTP 4776: SERVER socketOnParserExecute 426
NET 4776: _read
NET 4776: _read wait for connection
HTTP 4776: outgoing message end.
NET 4776: afterConnect
NET 4776: _read
NET 4776: Socket._read readStart
NET 4776: onread 140
NET 4776: got data
HTTP 4776: AGENT incoming response!
HTTP 4776: AGENT isHeadResponse false
NET 4776: _read
HTTP 4776: AGENT socket keep-alive
HTTP 4776: outgoing message end.
[HPM] GET /api/whatever -> http://localhost:12345
HTTP 4776: SERVER socketOnParserExecute 524
_stream_writable.js:361
  cb();
  ^

TypeError: cb is not a function
    at afterWrite (_stream_writable.js:361:3)
    at onwrite (_stream_writable.js:352:7)
    at WritableState.onwrite (_stream_writable.js:89:5)
    at Socket._writeGeneric (net.js:714:5)
    at Socket._write (net.js:724:8)
    at doWrite (_stream_writable.js:307:12)
    at writeOrBuffer (_stream_writable.js:293:5)
    at Socket.Writable.write (_stream_writable.js:220:11)
    at Socket.write (net.js:650:40)
    at ClientRequest.OutgoingMessage._writeRaw (_http_outgoing.js:167:23)

chimurai avatar Nov 16 '16 21:11 chimurai

This might be a Node issue.

Found this thread: https://github.com/nodejs/node/issues/8650

Think your isolated example might be really helpful to solve the issue.

chimurai avatar Nov 16 '16 21:11 chimurai

Hey @chimurai , I used you settings as described above but still stuck on the infinite authentication popup before those settings it was 401.

my web api authentication is as follow: <authentication mode="Windows"/>

devServer: {
      port: METADATA.port,
      host: METADATA.host,
      historyApiFallback: true,
      watchOptions: {
        aggregateTimeout: 300,
        poll: 1000
      },
      proxy: {
        '/api': {
          target: 'http://localhost:4640/',
          logLevel: 'debug',
          agent: new Agent({
            maxSockets: 100,
            keepAlive: true,
            maxFreeSockets: 10,
            keepAliveMsecs:1000,
            timeout: 60000,
            keepAliveTimeout: 30000 // free socket keepalive for 30 seconds
          }),
          onProxyRes: proxyRes => {
            console.log("onProxyRes event", proxyRes);
            var key = 'www-authenticate';
            proxyRes.headers[key] = proxyRes.headers[key] && proxyRes.headers[key].split(',');
          }
        }
      }
    }

oshri551 avatar Jan 01 '17 21:01 oshri551

Reopening this issue, since multiple users are facing this issue. Hopefully someone more knowledgeable on Node + NTLM can help you guys out.

chimurai avatar Jan 11 '17 20:01 chimurai

Thank you @chimurai!

bac42x avatar Jan 11 '17 20:01 bac42x

@chimurai , I tried to investigate the issue. So first i created small node server with node-http-proxy:

Agent =  new Agent({
        maxSockets: 100,
        keepAlive: true,
        maxFreeSockets: 10,
        keepAliveMsecs:100000,
        timeout: 6000000,
        keepAliveTimeout: 90000 // free socket keepalive for 90 seconds
    });

    var proxy = httpProxy.createProxy({ target: 'http://localhost:4640', agent: Agent});

    // Modify headers of the request before it gets sent
    proxy.on('proxyRes', function (proxyRes) {
        var key = 'www-authenticate';
        proxyRes.headers[key] = proxyRes.headers[key] && proxyRes.headers[key].split(',');
    });

    require('http').createServer(function (req, res) {
    proxy.web(req, res);
    }).listen(4000);

every thing works fine with those settings.

when i tried to do the same just with http-proxy-middleware and express server i got the error @EladBezalel got (cb).

So in the end i found a workaround:

in my webpack settings i created the small server mentiond above (based on node-http-proxy),

The webpack config should be the same but now we are forwarding the requests to the small proxy and the small proxy forward it to our IIS

webpack config:
devServer: {
      port: METADATA.port,
      host: METADATA.host,
      historyApiFallback: true,
      watchOptions: {
        aggregateTimeout: 300,
        poll: 1000
      },
      proxy: {
        '/api': {
          target: 'http://localhost:4000/',
          logLevel: 'debug',
          agent: new Agent({
            maxSockets: 100,
            keepAlive: true,
            maxFreeSockets: 10,
            keepAliveMsecs:1000,
            timeout: 60000,
            keepAliveTimeout: 30000 // free socket keepalive for 30 seconds
          }),
          onProxyRes: proxyRes => {
            console.log("onProxyRes event", proxyRes);
            var key = 'www-authenticate';
            proxyRes.headers[key] = proxyRes.headers[key] && proxyRes.headers[key].split(',');
          }
        }
      }
    }

The target in webpack config is localhost:4000 (the small node server with http-proxy) The target in the small proxy server is localhost:4640 (my IIS server)

Now it works, hope it will help till the problem will be solved

oshri551 avatar Jan 11 '17 21:01 oshri551

@oshri551 it's still not working for me 😕 Care sharing minimal code?

EladBezalel avatar Jan 19 '17 13:01 EladBezalel

@EladBezalel I've pushed to my githab repo the code example based on AngularClass webpack starter.

Please look at http-proxy.js and webpack.dev.js under config folder.

**Don't forget we need to install http-proxy and agentkeepalive in our dev dependencies **

keep me updated if it works...

oshri551 avatar Jan 20 '17 18:01 oshri551

I tried doing what @oshri551 shared and on Chrome I keep getting constant pop ups asking for user name and password, once entered it just prompts over and over again. However, in Edge I get the prompt message to enter credentials and once I enter them it works. I am using the Angular CLI in the project, and it would increase my development time by leaps and bounds because right now I am having to compile every time and dump code in wwwroot of asp.net core project.

Alex-Torres avatar Mar 01 '17 14:03 Alex-Torres

Re-sharing @EladBezalel's minimal example: c3215-untitled.gz. source: https://github.com/chimurai/http-proxy-middleware/issues/39#issuecomment-260965382

Hoping someone can figure this one out.

chimurai avatar Mar 01 '17 21:03 chimurai

Confirmed vanilla implementation works fine. https://github.com/chimurai/http-proxy-middleware/issues/39#issuecomment-269920667

var httpProxy = require('http-proxy');
var Agent = require('agentkeepalive');

var agent = new Agent({
    maxSockets: 100,
    keepAlive: true,
    maxFreeSockets: 10,
    keepAliveMsecs:100000,
    timeout: 6000000,
    keepAliveTimeout: 90000 // free socket keepalive for 90 seconds
});

var proxy = httpProxy.createProxy({ target: 'http://localhost:12345', agent: agent});

// Modify headers of the request before it gets sent
proxy.on('proxyRes', function (proxyRes) {
    var key = 'www-authenticate';
    proxyRes.headers[key] = proxyRes.headers[key] && proxyRes.headers[key].split(',');
});

require('http').createServer(function (req, res) {
    proxy.web(req, res);
}).listen(4000);

Also express + http-proxy works

var express = require('express');
var httpProxy = require('http-proxy');
var Agent = require('agentkeepalive');

var agent =  new Agent({
    maxSockets: 100,
    keepAlive: true,
    maxFreeSockets: 10,
    keepAliveMsecs:100000,
    timeout: 6000000,
    keepAliveTimeout: 90000 // free socket keepalive for 90 seconds
});

var proxy = httpProxy.createProxy({ target: 'http://localhost:12345', agent: agent});

// Modify headers of the request before it gets sent
proxy.on('proxyRes', function (proxyRes) {
    var key = 'www-authenticate';
    proxyRes.headers[key] = proxyRes.headers[key] && proxyRes.headers[key].split(',');
});

var app = express();

app.use(function (req, res) {
    proxy.web(req, res);
});

app.listen(4000);

Issue is likely in http-proxy-middleware. I'll have to investigate it further.

chimurai avatar Mar 02 '17 22:03 chimurai

Just published v0.17.4 with a fix to npm.

@EladBezalel @oshri551 @bac42x can you please verify?

chimurai avatar Mar 02 '17 23:03 chimurai

@chimurai, v0.17.4 fixed it for me! Thank you very much! I have been completely unsuccessful getting OWIN/Katana to handle CORS preflight after many hours of work. This is a lifesaver!

bac42x avatar Mar 03 '17 13:03 bac42x

This works! Thank you @chimurai. I was able to wire up my Asp.net Core Web API's back end.

Alex-Torres avatar Mar 03 '17 14:03 Alex-Torres

Glad to hear the fix works! :)

chimurai avatar Mar 03 '17 16:03 chimurai

@chimurai, I tested it with express and it works ! ... but with webpack-dev-server it is still on the authentication popup loop. @Alex-Torres , @bac42x ... are you working with webpack-dev-server ?

oshri551 avatar Mar 05 '17 12:03 oshri551

@oshri551 webpack-dev-server is not updated yet, i made a pr to update this dependency, hope it'd be in soon.

@chimurai thanks for helping! I'll check it back tomorrow

EladBezalel avatar Mar 05 '17 22:03 EladBezalel

@EladBezalel Take a look at webpack-dev-server package.json This is the "http-proxy-middleware": "~0.17.1" dependency so if you update the package it will choose 0.17.4 because of the tilde symbol. So i already made the update and checked that the node_module contains the latest http-proxy-middleware version... I will try to look further and find out why i still get the authentication popup loop. keep me updated if it works for you...

oshri551 avatar Mar 06 '17 07:03 oshri551

@oshri551 When the major version is 0, it doesn't automatically upgrade patches, simply because it's considered pre-release and can break at any time.. I haven't got time to check it out, but i plan doing it this week so i'd keep you posted

EladBezalel avatar Mar 06 '17 16:03 EladBezalel

@oshri551 @EladBezalel @chimurai

Also not working here, getting infinite popup loop.

I can also confirm that my http-proxy-middleware is at version 0.17.4 and using webpack-dev-server 2.4.1

Webpack config

proxy: {
    "/api": {
        target: PROXYTARGET,
        changeOrigin: true,
        logLevel: 'debug',
        agent: new Agent({
            maxSockets: 100,
            keepAlive: true,
            maxFreeSockets: 10,
            keepAliveMsecs: 1000,
            timeout: 60000,
            keepAliveTimeout: 30000 // free socket keepalive for 30 seconds
        }),
        onProxyRes: proxyRes => {
            var key = 'www-authenticate';
            proxyRes.headers[key] = proxyRes.headers[key] && proxyRes.headers[key].split(',');
        }
    }
}

ktersius avatar Mar 07 '17 15:03 ktersius

It's working! Thank you very much @chimurai

EladBezalel avatar Mar 21 '17 14:03 EladBezalel

Hello,

@chimurai I keep having the authentication popup on my side. I fecthed the last version of webpack-dev-server.

I managed to create a little step by step process to reproduce the issue. I pushed it on a github project here: https://github.com/charleshetier/bench.ntlm.webpackserver

The readme contains the information on how to have the authentication popup. It may appear very weird but this is the only way I found to have this unwanted authentication popup almost for sure.

my agent (keepaliveagent) is setup this way:

return new Agent({
    maxSockets: 100, 
    keepAlive: true,
    maxFreeSockets: 1024,
    keepAliveMsecs:1000,
    timeout: 10000,    
    keepAliveTimeout: 5000   
  });

from the shrinkwrap, the http-proxy-middleware version is: 0.17.4

The server works only on windows...

Am I doing something wrong here?... Thanks a lot

charleshetier avatar Apr 07 '17 15:04 charleshetier

Hello, Is there any known solution for windows authentication when using webpack dev server? I have tried to configure it in the same way as proposed earlier in this thread, but I still keep having the authentication popup.

Hammer82 avatar Apr 10 '17 15:04 Hammer82

@Hammer82, I was only able to solve it by modifying the server. In my case, this meant intercepting the OPTIONS request in our OWIN WebApi service by using the HttpListener.AuthenticationSchemeSelectorDelegate. I could not find a way to solve it from the client.

bac42x avatar Apr 10 '17 16:04 bac42x

Hello, Any news about webpack-dev-server support yet ?

fayezmm avatar Apr 23 '17 07:04 fayezmm

I think I found a solution to the infinite popups, I simply changed the maxSockets to 1. Probably not the best solution but it seems to work.

My Agent is set up this way:

return new Agent({
    maxSockets: 1, 
    keepAlive: true,
    maxFreeSockets: 10,
    keepAliveMsecs:100000,
    timeout: 600000,
    keepAliveTimeout: 90000   
  });

Hope that helps!

BorkforceOne avatar Apr 24 '17 20:04 BorkforceOne

Hello, Any news about webpack-dev-server support yet ?

imsontosh avatar Apr 25 '17 09:04 imsontosh

@bjg96 Maybe it solves the problem in your specific context and this could be a clue to understand why it doesn't work in some specific cases. However as far as I understood the way the agent works, with maxsockets to 1, you won't be able to handle simultaneous requests. I mean the popup will appear if you are making 2 ajax requests at the same time for example

charleshetier avatar Apr 25 '17 19:04 charleshetier

I havet still not found any solution for my implementation, so I decided for the moment to not use Windows authentication combined with webpack dev server.

Hammer82 avatar Apr 25 '17 20:04 Hammer82

Reopening issue since not all NTLM issues seem to be resolved.

@charleshetier Would be great if you can reproduce the issue in a vanilla setup. (Example: https://github.com/chimurai/http-proxy-middleware/issues/39#issuecomment-272003173)

Curious if the issue persists in your demo setup: https://github.com/charleshetier/bench.ntlm.webpackserver

chimurai avatar Apr 25 '17 21:04 chimurai

My recommendation is that you replace webpack-dev-server with express, webpack-hot-middleware and webpack-dev-middleware. I am able to hit my secured API endpoints with the express proxy and then serve my React app with the other two middleware packages. It requires a bit more configuration, but it works really well for me.

This is my server.js configuration:

var webpack = require("webpack");
var Agent = require("agentkeepalive");
//var WebpackDevServer = require('webpack-dev-server');
var config = require("./webpack.config.js")({});
var https = require("https");
var proxy = require('http-proxy-middleware');
const express = require("express");
const webpackDevMiddleware = require("webpack-dev-middleware");
const webpackHotMiddleware = require("webpack-hot-middleware");

var app = express();
var compiler = webpack(config);

app.use('/api', proxy(
      {
        target: 'http://localhost:58065/',
        changeOrigin: true,
        agent: new Agent({
            maxSockets: 100,
            keepAlive: true,
            maxFreeSockets: 10,
            keepAliveMsecs: 100000,
            timeout: 6000000,
            keepAliveTimeout: 90000 // free socket keepalive for 90 seconds
        }),
        onProxyRes: onProxyRes = (proxyRes) => {
            var key = 'www-authenticate';
            proxyRes.headers[key] = proxyRes.headers[key] && proxyRes.headers[key].split(',');
        }
    }
));

app.use(webpackDevMiddleware(compiler,
    {
      hot: true,
      historyApiFallback: true,
      contentBase: config.output.path,
      publicPath: config.output.publicPath,
      headers: { 'Access-Control-Allow-Origin': '*' }
    }));

app.use(webpackHotMiddleware(compiler, {}));

app.listen(7071, 'localhost', function (err, result) {
  if (err) {
    return console.log(err);
  }
  console.log('Webpack Dev Server is fired up!!');
});

Just realized this may not help with Basic authentication or anything, but I couldn't even get Windows Integrated authentication working with webpack-dev-server.

jndietz avatar Jun 08 '17 15:06 jndietz

@jndietz Can you please confirm if its working with windows integrated authentication ??

imsontosh avatar Jun 12 '17 10:06 imsontosh

It is working for me. I can try to put together a demo project that utilizes all the libraries needed.

jndietz avatar Jun 12 '17 14:06 jndietz

@imsontosh Here is a small project that utilizes express, webpack-dev-middleware, http-proxy-middleware

https://github.com/jndietz/csharp-windows-auth

Let me know if you are wanting to know something specific and I'll try to help out.

jndietz avatar Jun 12 '17 15:06 jndietz

@jndietz Thanks for your effort. Working like charm. Cheers 👍

imsontosh avatar Jun 20 '17 07:06 imsontosh

@jndietz, thanks for creating the example.

I noticed an interesting behavior running it. When a request takes a long time to run - it forces a user to enter credentials every time it receives a subsequent request. I basically experience the same issue running angular-cli proxy which uses webpack with http-proxy-middleware.

It is easy to reproduce if you put Thread.Sleep(TimeSpan.FromSeconds(5)); in the controller action and try to trigger an API action several times.

Do you have any ideas why it could be happening and how could be resolved?

CC @chimurai @imsontosh

jalaz avatar Aug 18 '17 03:08 jalaz

thank you everyone for chiming in on this, I realize this is a closed issue but this helped solve an auth/CORS issue I had when trying to hit a MVC .NET 4.6 WebApi app which uses "Windows Authenticaton" and I couldn't get it to work during development for two reasons (CORS and NTLM not working well with axios).

My code for the proxy middleware is this:

import proxyMiddleware from 'http-proxy-middleware'
import Agent from 'agentkeepalive'

const bundler = webpack(config)

let middleware = [
  proxyMiddleware('/api', {
    changeOrigin: true,
    target: 'http://codefest.example.gov/Team7',
    agent: new Agent({
      maxSockets: 100,
      keepAlive: true,
      maxFreeSockets: 10,
      keepAliveMsecs: 100000,
      timeout: 6000000,
      keepAliveTimeout: 90000 // free socket keepalive for 90 seconds
    }),
    onProxyRes: (proxyRes) => {
        var key = 'www-authenticate';
        proxyRes.headers[key] = proxyRes.headers[key] && proxyRes.headers[key].split(',');
    }
  })
 // ... removed for brevity
];

// ... removed for brevity

browserSync({
  port: 3000,
  ui: {
    port: 3001
  },
  open: false,
  server: {
    baseDir: 'src',
    middleware
  }
});

The ending result was webpack/browserSync serving up my ReactJs UI at http://localhost:3000/ and during dev it would hit http://localhost:3000/api/values browserSync would intercept the /api/values and proxy it to the backend WebApi at http://codefest.example.gov/Team7/api/values. This works well, thanks again for all of your inputs.

mikedevita avatar Sep 20 '17 16:09 mikedevita

I just wanted to confirm that I'm still seeing an infinite series of pop-ups requesting authentication after trying various suggestions in this thread and ensuring that my dependencies include the fixes mentioned throughout.

I'm a little confused whether this issue is considered resolvable or not. As of now, has anyone successfully built a solution utilizing Webpack/http-proxy-middleware with NTLM authentication?

xcjs avatar Dec 14 '17 21:12 xcjs

I still have this issue as well and have been monitoring to see if/when it gets resolved. IE11 seems to accept credentials from a single popup. No other browser does.

Usman-M avatar Dec 29 '17 20:12 Usman-M

@xcjs , The solution from @jndietz worked for me while all the other solutions attempting to use webpack-dev-server caused me to get never ending authentication prompts in both Chrome and Firefox.

The authentication prompt still appears sometimes; it seems to be while Chrome dev tools are open and I try a full refresh. But it works pretty well.

mike-schenk avatar Dec 29 '17 23:12 mike-schenk

@mike-schenk The solution from @jndietz unfortunately didn't have the same effect for me. I'm not expecting the prompt to never show, but I can't access the page due to the stream of authentication prompts.

xcjs avatar Dec 30 '17 00:12 xcjs

@jalaz @xcjs Hey guys -- sorry that wasn't working for you. I will need to dive a bit deeper into it and see what exactly is going on. Currently stuck working on setting up an angular 1 application to be built with webpack so I might be able to take a look at it soon.

jndietz avatar Jan 19 '18 19:01 jndietz

@jalaz @xcjs

I'm able to reproduce the issue in my project. I must not have seen that happen back when I made it.

With our Angular 1/Webpack config, we are going to be putting our UI code in with our web layer so we don't have any issues. If we weren't using Windows integrated authentication I don't suspect this would be as big of an issue. The alternative would be configuring the proxy as stated above, but then development activities would have to take place in Internet Explorer/Edge only and dealing with the username/password dialog from time to time.

The former was a better compromise for us. I still want to figure out a resolution for this :)

jndietz avatar Jan 30 '18 15:01 jndietz

same here. I switched back from gulp to grunt because of this. btw. (grunt-connect-proxy) is doing it correct.

bkrrrr avatar Mar 13 '18 16:03 bkrrrr

Update for BASIC authentication:

i figured out that it is necessary to forward explizit the authorization header. In case you want to enter the password over browser buildin popup: this should work.

proxy('/sap', {
        target: 'http://' + configBuild.sap.server + ':' + configBuild.sap.port,
        changeOrigin: true,
        onProxyRes: function (proxyRes, req, res) {
            if (proxyRes.headers['authorization']){
                proxyRes.headers['authorization'] = req.headers['authorization'];
            }
        }
}

But i'm still looking for a solution in CL(Karma) environment.

bkrrrr avatar Mar 14 '18 09:03 bkrrrr

Thank you after few hours of hair pulling .. this link resolved my issue

srkt avatar Mar 11 '19 18:03 srkt

Here is my working configuration with Vue-cli 3 / .NET WebAPI

Install package

npm install agentkeepalive --save

Update vue.config.js

const Agent = require('agentkeepalive');

module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost',
        changeOrigin: true,
        agent: new Agent({
          maxSockets: 100,
          keepAlive: true,
          maxFreeSockets: 10,
          keepAliveMsecs: 1000,
          timeout: 60000,
          freeSocketTimeout: 30000
        }),
        onProxyRes: proxyRes => {
          var key = 'www-authenticate';
          proxyRes.headers[key] = proxyRes.headers[key] && proxyRes.headers[key].split(',');
        }
      }
    }
  }
}

ahrpee avatar Mar 30 '19 06:03 ahrpee

The above configuration (and some variations messing with the numbers) partially works for me. When I hit my home page, which makes 3 API calls, it usually works, but sometimes, I'll get another auth prompt for one of the API calls, but not the others. When I navigate to another route on the site, I get the never-ending prompts for the new route's API calls.

I'm using Chrome and Vue UI , and the proxy is connecting to IIS Express on Windows 7 using NTLM authentication. Going directly to the site on IIS Express always works fine, but that doesn't give me Vue hot-reload.

richardtallent avatar Jun 07 '19 22:06 richardtallent

I have been using the above configuration as well and worked almost flawlessly (except sometimes a logging prompt which could be solved by restarting the dev server). Since I upgraded my project to the latest version of Angular and Angular CLI, the solution is not working anymore. A bug has been opened here but I think the issue lies int http-proxy-middleware. [email protected] is not working with the above solution [email protected] is working

fredjeck avatar Jun 20 '19 13:06 fredjeck

I have the exact same issue as @richardtallent, it works very intermittently, sometimes even perfectly for hours at a time and then will randomly start hitting me with multiple login pop-ups that don't seem to care whether I enter my credentials correctly or not.

Sometimes restarting my local web server fixes it, sometimes it doesn't. Rebuilding the project also sometimes fixes it.

I've played around with the agentkeepalive settings alot to no success (I have no idea what these settings actually do, and there's little explanation).

Will just have to go without hot reload for now

Perogy avatar Jul 06 '19 01:07 Perogy

@richardtallent, @Perogy, I'm having this issue too. I've been dealing with it for about 3 months and I've been trying to find a pattern to it and haven't been able to come up with anything tangible. I've tried everything recommended here and spent quite a few hours digging into the requests myself. I'm using it to proxy calls through my dev server to our backend API to bypass CORS in development since CORS isn't required by our prod configuration. For some reason the last few days have been pretty unbearable, probably because we recently moved our API to the cloud.

DustinDSchultz avatar Jul 18 '19 21:07 DustinDSchultz

Bump for the last four comments. I'm in the same boat. The suggestions here work sometimes, but sometimes the connection still putters out and starts throwing authentication prompts. I think it has something to do with keeping the authenticated TCP connection open. But my knowledge of server protocols is pretty limited.

Aaron-Pool avatar Jul 25 '19 17:07 Aaron-Pool

I've had pretty good success with the agentkeepalive module, though for some reason it does NOT like signalr. If I try to route signalr calls through the proxy it just seems to break all the other calls and I wind up with the infinite login prompt. Maybe its an issue of websockets+ntlm?

sm-cdecker avatar Jul 30 '19 14:07 sm-cdecker

I have the same issue as @richardtallent and @Perogy. I am temporarily using the work around that restricts the maxSockets to 1, which seems to minimize the frequency of the issue but am wondering if there is any update on a resolution?

mgoertz-dev avatar Feb 19 '20 21:02 mgoertz-dev

I tried this instead of just fixing the www-authenticate header. it worked intermittently like once it work and 10 times it wont :(


proxy.on('proxyRes', function (proxyRes, req, res) {
  // var key = 'www-authenticate';
  // proxyRes.headers[key] = proxyRes.headers[key] && proxyRes.headers[key].split(',');

  var headersFirstKey = {};
  var headers = {};
  for(var i = 0; i < proxyRes.rawHeaders.length; i += 2) {
    var headerKey = proxyRes.rawHeaders[i];
    var headerValue = proxyRes.rawHeaders[i + 1];

    var headerKeyLower = headerKey.toLowerCase().trim();
    headerKey = headersFirstKey[headerKeyLower] || headerKey;
    headersFirstKey[headerKeyLower] = headerKey;
    headers[headerKey] = headers[headerKey] ? headers[headerKey] : [];
    headers[headerKey].push(headerValue)
  }
  for(var j in headers) {
    proxyRes.headers[j] = headers[j];
  }
});

also tried:


require('https').createServer({
  key: fs.readFileSync('key.key', 'utf8'),
  cert: fs.readFileSync('crt.crt', 'utf8'),
  passphrase: 'test',
  rejectUnauthorized: false,
  handshakeTimeout: 100000,
  insecureHTTPParser: true,  // maybe :|
  maxHeaderSize: 10000000
},
function (req, res) {
  proxy.web(req, res);
})
.on('secureConnection', (socket) => { // to keep connection open with client
  socket.setKeepAlive(true, 10000).setTimeout(60000);
})
.listen(443);

maa105 avatar Apr 16 '20 05:04 maa105

Do not use the simple agentkeepalive use instead agentkeepalive-ntlm

https://github.com/pappan123/agentkeepalive-ntlm

Here is a quick description explaining the issue:

The main motivation for this fork from agentkeepalive was to support user specific NTLM sessions. For NTLM to work, the TCP connection has to be authorized for a user. When we use the base 'agentkeepalive', sockets are authorized using a combination of the host + port. eg yahoo.com:443. However if multiple users are trying to access a NTLM enabled site, the socket connections were getting mixed up between users. Changed the implementation to include 'host+port+cookieName' while assigning sockets

If you use the code shown by mikedevita it must work.

sam-michael avatar Apr 29 '20 07:04 sam-michael

Here is my working configuration with Vue-cli 3 / .NET WebAPI

Install package

npm install agentkeepalive --save

Update vue.config.js

const Agent = require('agentkeepalive');

module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost',
        changeOrigin: true,
        agent: new Agent({
          maxSockets: 100,
          keepAlive: true,
          maxFreeSockets: 10,
          keepAliveMsecs: 1000,
          timeout: 60000,
          freeSocketTimeout: 30000
        }),
        onProxyRes: proxyRes => {
          var key = 'www-authenticate';
          proxyRes.headers[key] = proxyRes.headers[key] && proxyRes.headers[key].split(',');
        }
      }
    }
  }
}

This worked ! You are a gem :) <3 I was having CORS issues with .net core back end API where only windows auth could be enabled and no anonymous auth , this was causing preflight error. Your solution works perfectly with the proxy server.

babycasper123 avatar May 10 '20 13:05 babycasper123

Here is my working configuration with Vue-cli 3 / .NET WebAPI

Install package

npm install agentkeepalive --save

Update vue.config.js

const Agent = require('agentkeepalive');

module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost',
        changeOrigin: true,
        agent: new Agent({
          maxSockets: 100,
          keepAlive: true,
          maxFreeSockets: 10,
          keepAliveMsecs: 1000,
          timeout: 60000,
          freeSocketTimeout: 30000
        }),
        onProxyRes: proxyRes => {
          var key = 'www-authenticate';
          proxyRes.headers[key] = proxyRes.headers[key] && proxyRes.headers[key].split(',');
        }
      }
    }
  }
}

I had an issue with NTLM authentication when subsequent auth requests failed (usually after reloading the page). The issue turned out to be that Agent performed NTLM auth requests with different connections from the pool.

Changing maxFreeSockets to 1 solved the issue.

alexmnv avatar Mar 30 '21 05:03 alexmnv

Hello -- I have information on what is causing this issue. My background isn't in the JavaScript/node world, meaning I don't have the relevant experience to go it alone. So if someone wants to chat about this issue. I'd love to tackle it together.

In short, there seems to be two issues. The first solved by

var key = 'www-authenticate';
          proxyRes.headers[key] = proxyRes.headers[key] && proxyRes.headers[key].split(',');

Is that the proxy was collapsing the www-authenticate headers into one line. Something like

WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM

into

WWW-Authenticate: Negotiate, NTLM

Seemingly, the browser will not begin the NTLM negotiation when it's collapsed. Even though, I think that's okay as per the standard. But the browser (I was using chrome) doesn't like it and won't reply to the challenge.

The problem with the infinite authentication dialogs comes from the real problem. Which is that NTLM negotiation requires a single TCP connection which is kept alive. The NTLM negotiation happens in 3 parts. The original request, the first challenge, and a second challenge. After the TCP socket is authenticated, as long as it stays alive each successive request will work without auth.

When we're proxying, there doesn't seem to be a connection between the incoming (from the browser), and the outgoing (to the IIS server). Why is this a problem? Because the 3 part handshake which happens in 3 separate requests through the proxy, can happen on 3 different outgoing sockets to the server. So the browser, and the server both get confused.

In order to mitigate this, I was able to set the outgoing agent configuration to use a single socket maxSockets : 1, and keep alive to true, with a long timeout keepAlive : true, timeout : 999999. This tells the http.Agent to keep the outgoing sockets alive, for a long time. The maxSockets, tells it to only use one outgoing connection. This works, for the most part. However, it's slow, and it's not quite right. Because if you look at an actual interaction between the browser and IIS server, it'll use the same socket for multiple requests. However, if you wireshark between the browser and the proxy, and the proxy and the server. You see multiple requests on several incoming sockets from the browser, and they all share the single outgoing socket (which is what makes this work) to the server.

Ideally, with NTLM the proxy should maintain the browser connection one-to-one, with the server.

Hopefully this makes sense. I empathize with everyone in this thread who's been having this issue.

mvpete avatar Oct 14 '21 16:10 mvpete