Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V5 Fails Behind Corporate Proxy with Provider #11028

Open
SolidAnonDev opened this issue May 28, 2024 · 2 comments
Open

V5 Fails Behind Corporate Proxy with Provider #11028

SolidAnonDev opened this issue May 28, 2024 · 2 comments
Labels
bug Something isn't working triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime.

Comments

@SolidAnonDev
Copy link

SolidAnonDev commented May 28, 2024

Environment

OS: Windows 11 10.0.22621
CPU: (12) x64 Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz
Memory: 6.80 GB / 15.78 GB
Binaries:
Node: 20.9.0 - C:\Program Files\nodejs\node.EXE
Yarn: 1.22.19 - C:\Program Files (x86)\Yarn\bin\yarn.CMD
npm: 10.8.0 - C:\Program Files\nodejs\npm.CMD
Browsers:
Edge: Chromium (125.0.2535.51)
Internet Explorer: 11.0.22621.3527
npmPackages:
next: ^14.2.3 => 14.2.3
next-auth: ^5.0.0-beta.18 <= 5.0.0-beta.18
react: ^18.3.1 => 18.3.1

Reproduction URL

https://github.com/tbrundige/authjs-adapter-issue

Describe the issue

First off, I pulled a random repro link from other open issues to make it valid for the bot. This isn't really something I can reproduce. The reproduction is to try to use next-auth with a provider behind a corporate proxy, there's not really a minimal reproduction for that. I'm sorry for doing this but there's not much I can do to reproduce this.

Next-Auth, just as in #2509 - appears to fail behind a corporate proxy in V5 as well. We are using the Microsoft Entra ID provider, coming from the V4 Azure AD Provider.

For V4, we had been using this solution which was coined in the issue linked above, involving patching the next-auth source code with patch-package. This worked fine, but it appears things have changed a great deal in the V5 source and we cannot find where we should patch the source to allow the requests to make it through.

If support cannot be added for a corporate proxy with providers, we would like to at least see how/where to apply a patch to the source to make this workable as we did in V4. If not, this may completely bar and shutdown any ability to use next-auth moving forward when V5 is released, which would be really unfortunate for us.

V5 is working in development for us (Microsoft Entra Id Provider) without issue, with successful authentication, but upon deployment, we receive errors very remeniscent of what we saw with V4 behind a corporate proxy.

How to reproduce

ust run any application with NextAuth behind a corporate proxy and try to log in with a Provider (like Google or GitHub).

You will see something similar to the following:

message: 'fetch failed',
    stack: 'TypeError: fetch failed\n' +
      '    at Object.processResponse (node:internal/deps/undici/undici:5555:34)\n' 

Expected behavior

The requests are able to make it out to the providers for successful authentication from behind a corporate proxy. In previous iterations, we acheived this using an HttpProxyAgent.

@SolidAnonDev SolidAnonDev added bug Something isn't working triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime. labels May 28, 2024
@MobliMic
Copy link

So I've been having issues with corporate proxy as well for both v4 and v5 but have managed to get it working on v5 with the following code

import { ProxyAgent } from 'undici';

const discoveryResponse = await o.discoveryRequest(issuer,
        {
          [o.customFetch]: (...args) => {
            if (process.env.http_proxy) {
              const agent =  new ProxyAgent(process.env.http_proxy);
              args[1].dispatcher = agent;
            }
            return fetch(...args);
          }
        });

Below is the full (messy and auto-formatted) patch I've applied. I haven't tested the change for the profile image fetch but that's something I don't need so just applied the change in case I do in the future and based it on the v4 change.

Run an npm i undici

diff --git a/node_modules/@auth/core/lib/actions/callback/oauth/callback.js b/node_modules/@auth/core/lib/actions/callback/oauth/callback.js
index e4e64ca..f912fc7 100644
--- a/node_modules/@auth/core/lib/actions/callback/oauth/callback.js
+++ b/node_modules/@auth/core/lib/actions/callback/oauth/callback.js
@@ -1,6 +1,8 @@
 import * as checks from "./checks.js";
 import * as o from "oauth4webapi";
 import { OAuthCallbackError, OAuthProfileParseError, } from "../../../../errors.js";
+import { ProxyAgent } from 'undici';
+
 /**
  * Handles the following OAuth steps.
  * https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1
@@ -20,7 +22,16 @@ export async function handleOAuth(query, cookies, options, randomState) {
         // We assume that issuer is always defined as this has been asserted earlier
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         const issuer = new URL(provider.issuer);
-        const discoveryResponse = await o.discoveryRequest(issuer);
+        const discoveryResponse = await o.discoveryRequest(issuer,
+          {
+            [o.customFetch]: (...args) => {
+              if (process.env.http_proxy) {
+                const agent =  new ProxyAgent(process.env.http_proxy);
+                args[1].dispatcher = agent;
+              }
+              return fetch(...args);
+            },
+          });
         const discoveredAs = await o.processDiscoveryResponse(issuer, discoveryResponse);
         if (!discoveredAs.token_endpoint)
             throw new TypeError("TODO: Authorization server did not provide a token endpoint.");
@@ -61,6 +72,9 @@ export async function handleOAuth(query, cookies, options, randomState) {
                 args[1]?.body instanceof URLSearchParams) {
                 args[1].body.delete("code_verifier");
             }
+            if (process.env.http_proxy) {
+              args[1].dispatcher = new ProxyAgent(process.env.http_proxy);
+            }
             return fetch(...args);
         },
     });
diff --git a/node_modules/@auth/core/lib/actions/signin/authorization-url.js b/node_modules/@auth/core/lib/actions/signin/authorization-url.js
index 8f093cb..6553c77 100644
--- a/node_modules/@auth/core/lib/actions/signin/authorization-url.js
+++ b/node_modules/@auth/core/lib/actions/signin/authorization-url.js
@@ -1,5 +1,7 @@
 import * as checks from "../callback/oauth/checks.js";
 import * as o from "oauth4webapi";
+import { ProxyAgent } from 'undici';
+
 /**
  * Generates an authorization/request token URL.
  *
@@ -15,7 +17,16 @@ export async function getAuthorizationUrl(query, options) {
         // We check this in assert.ts
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         const issuer = new URL(provider.issuer);
-        const discoveryResponse = await o.discoveryRequest(issuer);
+        const discoveryResponse = await o.discoveryRequest(issuer,
+        {
+          [o.customFetch]: (...args) => {
+            if (process.env.http_proxy) {
+              const agent =  new ProxyAgent(process.env.http_proxy);
+              args[1].dispatcher = agent;
+            }
+            return fetch(...args);
+          }
+        });
         const as = await o.processDiscoveryResponse(issuer, discoveryResponse);
         if (!as.authorization_endpoint) {
             throw new TypeError("Authorization server did not provide an authorization endpoint.");
diff --git a/node_modules/@auth/core/providers/microsoft-entra-id.js b/node_modules/@auth/core/providers/microsoft-entra-id.js
index 2063d5e..8b45cb6 100644
--- a/node_modules/@auth/core/providers/microsoft-entra-id.js
+++ b/node_modules/@auth/core/providers/microsoft-entra-id.js
@@ -100,6 +100,8 @@
  *
  * :::
  */
+import { ProxyAgent } from "undici";
+
 export default function MicrosoftEntraID(options) {
     const { tenantId = "common", profilePhotoSize = 48, ...rest } = options;
     rest.issuer ?? (rest.issuer = `https://login.microsoftonline.com/${tenantId}/v2.0`);
@@ -110,12 +112,21 @@ export default function MicrosoftEntraID(options) {
         wellKnown: `${rest.issuer}/.well-known/openid-configuration?appid=${options.clientId}`,
         authorization: {
             params: {
-                scope: "openid profile email User.Read",
-            },
+                scope: "openid profile email User.Read"
+            }
         },
         async profile(profile, tokens) {
             // https://learn.microsoft.com/en-us/graph/api/profilephoto-get?view=graph-rest-1.0&tabs=http#examples
-            const response = await fetch(`https://graph.microsoft.com/v1.0/me/photos/${profilePhotoSize}x${profilePhotoSize}/$value`, { headers: { Authorization: `Bearer ${tokens.access_token}` } });
+            let fetchOptions = {
+                headers: {
+                    Authorization: `Bearer ${tokens.access_token}`
+                }
+            };
+
+            if (process.env.http_proxy) {
+                fetchOptions.dispatcher = new ProxyAgent(process.env.http_proxy);
+            }
+            const response = await fetch(`https://graph.microsoft.com/v1.0/me/photos/${profilePhotoSize}x${profilePhotoSize}/$value`, fetchOptions);
             // Confirm that profile photo was returned
             let image;
             // TODO: Do this without Buffer
@@ -124,17 +135,17 @@ export default function MicrosoftEntraID(options) {
                     const pictureBuffer = await response.arrayBuffer();
                     const pictureBase64 = Buffer.from(pictureBuffer).toString("base64");
                     image = `data:image/jpeg;base64, ${pictureBase64}`;
+                } catch {
                 }
-                catch { }
             }
             return {
                 id: profile.sub,
                 name: profile.name,
                 email: profile.email,
-                image: image ?? null,
+                image: image ?? null
             };
         },
         style: { text: "#fff", bg: "#0072c6" },
-        options: rest,
+        options: rest
     };
 }

Using patch-package to generate and apply the change. Hope this helps if you haven't already solved the issue since posting :)

@SolidAnonDev
Copy link
Author

@MobliMic - If this is working for you, this will likely work for me. A quick once over looks really good, as it appears to hit the same things V4 needed to work successfully behind the proxy.

I have been successfully using V4 for over a year with an older corporate proxy patch. I just had no idea how to track down where the problematic fetches are that need a proxy agent for V5. I will try this early next week and report back. Thanks so much for this!

Also, I am not using the profile picture call like you, so I have removed it completely in my patch file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime.
Projects
None yet
Development

No branches or pull requests

2 participants