Skip to content

proxyUrl implementation is broken (and here is a two-line fix) #8024

@assen-totin

Description

@assen-totin

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.

msal.patch

Error Message

No response

MSAL Logs

No response

Network Trace (Preferrably Fiddler)

  • Sent
  • Pending

MSAL Configuration

N/A

Relevant 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs: Attention 👋Awaiting response from the MSAL.js teambug-unconfirmedA reported bug that needs to be investigated and confirmedmsal-nodeRelated to msal-node packagepublic-clientIssues regarding PublicClientApplicationsquestionCustomer is asking for a clarification, use case or information.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions