Open SolidAnonDev opened 3 months ago
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 :)
@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.
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 :)
I've tried to use your patch in my NextJs Project and i run into this error:
With --turbo
:
Without --turbo
:
Is there something that i need to configure in nextjs to make it work?
We applied MobliMic's patch as is on our app and got it working. All we had to do was create a new patch file called next-auth+5.0.0-beta.19.patch and paste their patch in. Previously we couldn't get the other suggested patch for V4 working, so this is great!
We did run into a slight hiccup with the .env. The patch uses lowercase key names and our .env had uppercase. Adding a lowercase key did the trick.
I still wasn't able to make this patch work for me.
I'm using the patch function of pnpm instead of patch-package
with "next": "14.2.4"
and "next-auth": "^5.0.0-beta.19"
.
@auth__core@0.32.0.patch
fileAny Ideas what could be wrong?
I was briefly attempting to use the patch the other day, but I ran into an issue with Undici and Next 14 with the error:
This error is referenced in this issue here
Module parse failed: Unexpected token (884:57)
| // 5. If object is not a default iterator object for interface,
| // then throw a TypeError.
> if (typeof this !== 'object' || this === null || !(#target in this)) {
| throw new TypeError(
| `'next' called on an object that does not implement interface ${name} Iterator.`
Import trace for requested module:
./node_modules/undici/lib/web/fetch/util.js
./node_modules/undici/lib/web/fetch/index.js
./node_modules/undici/index.js
./node_modules/@elastic/transport/lib/connection/UndiciConnection.js
./node_modules/@elastic/transport/lib/connection/index.js
./node_modules/@elastic/transport/index.js
./node_modules/@elastic/elasticsearch/index.js
./src/search/client.ts
./src/search/search.ts
This appears to be an issue with next and not undici, though I did apply the patch as@auth__core@0.32.0.patch
and not next-auth+5.0.0-beta.19.patch
so maybe I will try that and see what happens.
@Charismara I just realized this is the same issue you're having - this is a problem with next parsing Undici, and I'm not quite sure how to get around it or what versions of Next the other users are using in order to resolve this issue.
We applied MobliMic's patch as is on our app and got it working. All we had to do was create a new patch file called next-auth+5.0.0-beta.19.patch and paste their patch in. Previously we couldn't get the other suggested patch for V4 working, so this is great!
We did run into a slight hiccup with the .env. The patch uses lowercase key names and our .env had uppercase. Adding a lowercase key did the trick.
@Oakwhisper Could you call out the package versions you're using for Undici and Next? I cannot get this working for the same reason as the other user.
@SolidAnonDev We are using:
"undici": "6.19.2",
"next": "14.2.3",
"next-auth": "5.0.0-beta.19",
I did start getting that same error when attempting to use middleware for auth instead of handling it in a server component.
I'm still running into the errors from my last comment and got no ideas left to try. Any help is appreciated 😓
I'm still running into the errors from my last comment and got no ideas left to try. Any help is appreciated 😓
I have not yet found a solution for this either.
This is a bit ridiculous that we can't get simple support for a corporate proxy. The community came up with a minimal solution for V4, they said it's "not a priority" back then, and still won't do anything about it now.
I agree this seems actually quite limiting! Imagine all the corps that could use nextjs/next-auth if they would allow this natively 👍
This issue was marked with the help needed
label by a maintainer.
The issue might require some digging, so it is recommended to have some experience with the project.
Have a look at the Contributing Guide first.
This will help you set up your development environment to get started. When you are ready, open a PR, and link back to this issue in the form of adding Fixes #1234
to the PR description, where 1234
is the issue number. This will auto-close the issue when the PR gets merged, making it easier for us to keep track of what has been fixed.
Please make sure that - if applicable - you add tests for the changes you make.
If you have any questions, feel free to ask in the comments below or the PR. Generally, you don't need to @mention
anyone directly, as we will get notified anyway and will respond as soon as we can)
[!NOTE]
There is no need to ask for permission "can I work on this?" Please, go ahead if there is no linked PR :slightly_smiling_face:
We're happy to review a PR to get rid of the patch 😊 due to the complexity of corporate proxy setup - as mentioned in the PR description by @SolidAnonDev, us maintainers also haven't really found the time to implement & test it thoroughly. This is where we can really use some help from the community! 🙏
@ThangHuuVu If it means anything, as others have echoed, the above patch worked for me.
@ajhous44
Not sure what version of next you're using, but I was unable to get it working due to the previously documented errors but now see this with the next@latest
Module build failed: UnhandledSchemeError: Reading from "node:console" is not handled by plugins (Unhandled scheme).
Webpack supports "data:" and "file:" URIs by default.
You may need an additional plugin to handle "node:" URIs.
Import trace for requested module:
node:console
./node_modules/undici/lib/mock/pending-interceptors-formatter.js
./node_modules/undici/lib/mock/mock-agent.js
./node_modules/undici/index.js
./node_modules/@auth/core/providers/microsoft-entra-id.js
./node_modules/next-auth/providers/microsoft-entra-id.js
./src/lib/auth.ts
This kind of thing with Undici is not something I know how to resolve or diagnose unfortunately.
When I tried to reproduce the issue in callback.ts
of @auth/core
, I encountered errors when importing undici
or node-related modules.
Shouldn't we attempt to implement the proxy using only the fetch API, or be able to internally import node modules?
Additionally, in the patch provided above, the init (RequestInit) object doesn't contain a dispatcher
property.
@SolidAnonDev
Module build failed: UnhandledSchemeError: Reading from "node:console" is not handled by plugins (Unhandled scheme).
I discovered that the UnhandledSchemeError
occurs when using node modules and auth.js middleware simultaneously.
Next.js supports node runtime, but when auth
is added to middleware, it runs in full edge runtime, restricting the use of some node modules.
Reference: https://authjs.dev/guides/edge-compatibility#middleware
For middleware-related issues, I referred to this discussion: https://github.com/vercel/next.js/discussions/62985
Environment
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:
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.