-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Description
Core Library
MSAL Node (@azure/msal-node)
Core Library Version
3.7.3
Wrapper Library
Not Applicable
Wrapper Library Version
0.0.0
Public or Confidential Client?
Public
Description
This was checked against the latest tag at the time of writing, 3.7.3.
Today I had to put a small MSAL-based app behind an HTTP proxy. At first, I was happy to find the proxyUrl configuration parameter - at least, until I found out it did not work: the proxy would get back an ECONNRESET from Microsoft's login.microsoftonline.com web server. To verify the workflow, I did a traffic dump on the proxy - and to my amazement, I found that MSAL was sending a plain-text POST request after being granted access over the CONNECT method; clearly, some developers did not know what they do.
Before the fix, a quick primer on proxying web requests. There are two (more or less standard) ways of doing it:
- If the destination resource uses the plain-text HTTP scheme, the client sends its normal request for it - with the only difference that it sends it to the proxy. The proxy then resolves the IP address for the requested resource, fetches the resource and sends back the response.
- If the destination resource uses the encrypted HTTPS scheme, the stanza is notably different. The client connects to the proxy with the CONNECT method, only giving the DNS name and TCP port of the resource it seeks; the proxy then opens a TCP connection to the remote end, sends back a 200 OK message to the client and bridges the two sockets. Next, the client requests a TLS session (which will be forwarded to the remote end by the proxy) and, upon its establishment, proceeds with the actual HTTP request. (Why is it so complicated? So that the client may verify the identity of the remote end itself. The big drawback? The proxy cannot control the data that flows through, giving the client a carte-blanche to send or receive whatever it wants.)
So, what did the MS developers do? They mixed the two. They do connect the proxy with CONNECT, but after receiving their 200 OK from the proxy, they simply write to the socket their HTTP request. What's missing? The TLS ClientHello message that will initiate up the TLS session. As a result, the web server at Microsoft's side receives plain text HTTP over a socket that should be implicit TLS - and, naturally, closes the socket immediately, resuling with ECONNRESET at proxy's end.
How to fix it?
Line 1: include the standard 'tls' module from NodeJS in your code. Can be done anywhere as the loaded module is cached and reused.
var tls = require('tls');
Line 2: switch the socket to TLS mode; the easiest way is to create a new TLS socket that will reuse the existing TCP socket; make sure to create this as a "client" TLS socket. This will result in ClientHello being sent to the proxy, which will forward it to the remote end; as the remote end has not seen any communication past the TCP handshake, it will treat this as a vanilla TLS socket and will repond with its ServerHello, eventually producing an encrypted channel for the HTTP to use
var socket = new tls.TLSSocket(sock, { isServer: false });
Where this all lives?
I patched the @azure/msal-node/lib/msal-node.cjs as this is the runtime file that is used; it may be that some other source file (like TypeScript) needs to be patched instead. The function to patch is appropriately named networkRequestViaProxy().
Included here is my patch.
And while we're at it, there is another bug worth fixing: for whatever (stupid) reason, after writing the content to the socket, MSAL also writes a CR+LF. On one hand, it is useless (the content is application/x-www-form-urlencoded); on another, it is wrong, because the value of Content-Length header that has already been sent is based only on the postRequestStringContent variable and does not include these two extra bytes. The discrepancy, apparently, does not disturb the Microsoft web servers, but its existence alone in the code speaks a lot about its quality. See the same function, the part where const outgoingRequestString is created.
Error Message
No response
MSAL Logs
No response
Network Trace (Preferrably Fiddler)
- Sent
- Pending
MSAL Configuration
N/ARelevant Code Snippets
See attached patch.Reproduction Steps
Try connecting via proxy
Expected Behavior
Connect via the proxy
Identity Provider
Entra ID (formerly Azure AD) / MSA
Browsers Affected (Select all that apply)
None (Server)
Regression
No response