ECONNREFUSED 在从 Next-Auth 授权方法中获取外部 API 时发生连接拒绝错误。

huangapple go评论71阅读模式
英文:

ECONNREFUSED when fetching external API from Next-Auth authorize method

问题

我正在尝试解决与Next.js(端口3000)和Next-Auth相关的问题。我目前有一个使用Nest.js构建的外部本地API(端口3001),并且我正在从那里使用auth/login端点。

*重要提示:*我正在使用app文件夹。

我按照这篇文章中的步骤配置了next-auth在我的项目中。

文件 app/api/auth/[...nextauth]/route.ts:

import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";

const handler = NextAuth({
  providers: [
    CredentialsProvider({
      id: "credentials",
      name: "my-project",
      credentials: {
        email: {
          label: "email",
          type: "email",
          placeholder: "jsmith@example.com",
        },
        password: { label: "Password", type: "password" },
      },
      async authorize(credentials, req) {
        const payload = {
          email: credentials?.email,
          password: credentials?.password,
        };
        try {
          // also tried using `http://localhost:3001`
          const res = await fetch("http://127.0.0.1:3001/auth/login", {
            method: "POST",
            body: JSON.stringify(payload),
            headers: {
              "Content-Type": "application/json",
            },
          });

          const user = await res.json();
          if (!res.ok) {
            throw new Error(user.message);
          }
          if (res.ok && user) {
            return user;
          }
          return null;
        } catch (error: any) {
          console.log(error);
        }
      },
    }),
  ],
  secret: process.env.NEXTAUTH_SECRET,
  pages: {
    signIn: "/login",
  },
  callbacks: {
    async jwt({ token, user, account }: any) {
      if (account && user) {
        return {
          ...token,
          accessToken: user.token,
          refreshToken: user.refreshToken,
        };
      }

      return token;
    },

    async session({ session, token }: any) {
      session.user.accessToken = token.accessToken;
      session.user.refreshToken = token.refreshToken;
      session.user.accessTokenExpires = token.accessTokenExpires;

      return session;
    },
  },
  debug: process.env.NODE_ENV === "development",
});

export { handler as GET, handler as POST };

文件: app/login/page.tsx:

import { /* ... */ } from "@chakra-ui/react";
import { useRouter } from "next/navigation";
import { ChangeEvent, useState } from "react";
import { signIn, getCsrfToken } from "next-auth/react";
import { GetServerSidePropsContext } from "next";

export default function Login({ csrfToken }: { csrfToken: string}) {
  const router = useRouter();

  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const handleEmailChange = (e: ChangeEvent<HTMLInputElement>) =>
    setEmail(e.target.value);
  const handlePasswordChange = (e: ChangeEvent<HTMLInputElement>) =>
    setPassword(e.target.value);

  const handleSignIn = async () => {
    const res = await signIn("credentials", {
      redirect: false,
      email,
      password,
      callbackUrl: `${window.location.origin}`,
    });

    console.log(res);
  };

  return (
    <Container>
      <FormControl>
          {/* ... */}
      </FormControl>
    </Container>
  );
}

export async function generateStaticParams(context: GetServerSidePropsContext) {
  return {
    props: {
      csrfToken: await getCsrfToken(context),
    },
  };
}

文件: app/providers.tsx:

import { CacheProvider } from "@chakra-ui/next-js";
import { ChakraProvider } from "@chakra-ui/react";
import { extendTheme } from "@chakra-ui/react";
import { SessionProvider } from "next-auth/react";

export const theme = extendTheme({ colors, breakpoints });

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <SessionProvider>
      <CacheProvider>
        <ChakraProvider theme={theme}>{children}</ChakraProvider>
      </CacheProvider>
    </SessionProvider>
  );
}

文件: app/layout.tsx:

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <Head />
      <body className={inter.className}>
        <Providers> 
          <Container maxW={"90%"}>
            <Grid minHeight={"100vh"} templateRows={"100px auto 50px"}>
              <Header />
              {children}
              <Footer />
            </Grid>
          </Container>
        </Providers>
      </body>
    </html>
  );
}

**我尝试解决的问题:**我面临的问题与我在文件app/api/auth/[...nextauth]/route.ts中进行的fetch()调用相关。基本上,在这一点上,我收到以下错误:

TypeError: fetch failed
at Object.fetch (node:internal/deps/undici/undici:11457:11)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5) { cause: Error: connect
ECONNREFUSED 127.0.0.1:3001
at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1494:16)
at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) {
errno: -111,
code: 'ECONNREFUSED',
syscall: 'connect',
address: '127.0.0.1',
port: 3001
}

我已经尝试在app/login/page.tsx中的handleSignIn()方法中直接进行相同的fetch调用,并且没有任何问题地获取了令牌。这意味着我的API与此问题或端口的任何问题无关。但是由于某种原因,当它到达Next-Auth定义时,我会收到错误。

我也分享了我的Docker Compose文件,以防万一:

version: "3.8"

services:
  pg-db:
    image: postgres:15.1
    restart: always
    container_name: postgres
    ports:
      - "5432:5432"

  nest-api:
    ...
    depends_on:
      - pg-db
    env_file: ./.env
    ports:
      - "3001:3001"
    stdin_open: true
    tty: true
    networks:
      - default

  nextjs-web:
    ...
    ports:
      - "3000:3000"
    env_file: ./.env

volumes:
  pg-db:
英文:

I'm trying to solve an issue related to Next.js (port 3000) and Next-Auth. I currently have an external local API (port 3001) built using Nest.js, and I'm consuming the auth/login endpoint from there.

Important: I'm using the app folder.

I followed this post to configure next-auth in my project.

File app/api/auth/[...nextauth]/route.ts:

import NextAuth from &quot;next-auth&quot;;
import CredentialsProvider from &quot;next-auth/providers/credentials&quot;;
const handler = NextAuth({
providers: [
CredentialsProvider({
id: &quot;credentials&quot;,
name: &quot;my-project&quot;,
credentials: {
email: {
label: &quot;email&quot;,
type: &quot;email&quot;,
placeholder: &quot;jsmith@example.com&quot;,
},
password: { label: &quot;Password&quot;, type: &quot;password&quot; },
},
async authorize(credentials, req) {
const payload = {
email: credentials?.email,
password: credentials?.password,
};
try {
// also tried using `http://localhost:3001`
const res = await fetch(&quot;http://127.0.0.1:3001/auth/login&quot;, {
method: &quot;POST&quot;,
body: JSON.stringify(payload),
headers: {
&quot;Content-Type&quot;: &quot;application/json&quot;,
},
});
const user = await res.json();
if (!res.ok) {
throw new Error(user.message);
}
if (res.ok &amp;&amp; user) {
return user;
}
return null;
} catch (error: any) {
console.log(error);
}
},
}),
],
secret: process.env.NEXTAUTH_SECRET,
pages: {
signIn: &quot;/login&quot;,
},
callbacks: {
async jwt({ token, user, account }: any) {
if (account &amp;&amp; user) {
return {
...token,
accessToken: user.token,
refreshToken: user.refreshToken,
};
}
return token;
},
async session({ session, token }: any) {
session.user.accessToken = token.accessToken;
session.user.refreshToken = token.refreshToken;
session.user.accessTokenExpires = token.accessTokenExpires;
return session;
},
},
debug: process.env.NODE_ENV === &quot;development&quot;,
});
export { handler as GET, handler as POST };

File: app/login/page.tsx:

&quot;use client&quot;;
import {
...
} from &quot;@chakra-ui/react&quot;;
import { useRouter } from &quot;next/navigation&quot;;
import { ChangeEvent, useState } from &quot;react&quot;;
import { signIn, getCsrfToken } from &quot;next-auth/react&quot;;
import { GetServerSidePropsContext } from &quot;next&quot;;
export default function Login({ csrfToken }: { csrfToken: string}) {
const router = useRouter();
const [email, setEmail] = useState(&quot;&quot;);
const [password, setPassword] = useState(&quot;&quot;);
const handleEmailChange = (e: ChangeEvent&lt;HTMLInputElement&gt;) =&gt;
setEmail(e.target.value);
const handlePasswordChange = (e: ChangeEvent&lt;HTMLInputElement&gt;) =&gt;
setPassword(e.target.value);
const handleSignIn = async () =&gt; {
const res = await signIn(&quot;credentials&quot;, {
redirect: false,
email,
password,
callbackUrl: `${window.location.origin}`,
});
console.log(res);
};
return (
&lt;Container&gt;
&lt;FormControl&gt;
...
&lt;/FormControl&gt;
&lt;/Container&gt;
);
}
export async function generateStaticParams(context: GetServerSidePropsContext) {
return {
props: {
csrfToken: await getCsrfToken(context),
},
};
}

File: app/providers.tsx:

&quot;use client&quot;;
import { CacheProvider } from &quot;@chakra-ui/next-js&quot;;
import { ChakraProvider } from &quot;@chakra-ui/react&quot;;
import { extendTheme } from &quot;@chakra-ui/react&quot;;
import { SessionProvider } from &quot;next-auth/react&quot;;
export const theme = extendTheme({ colors, breakpoints });
export function Providers({ children }: { children: React.ReactNode }) {
return (
&lt;SessionProvider&gt;
&lt;CacheProvider&gt;
&lt;ChakraProvider theme={theme}&gt;{children}&lt;/ChakraProvider&gt;
&lt;/CacheProvider&gt;
&lt;/SessionProvider&gt;
);
}

File: app/layout.tsx:

&quot;use client&quot;;
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
&lt;html lang=&quot;en&quot;&gt;
&lt;Head /&gt;
&lt;body className={inter.className}&gt;
&lt;Providers&gt; 
&lt;Container maxW={&quot;90%&quot;}&gt;
&lt;Grid minHeight={&quot;100vh&quot;} templateRows={&quot;100px auto 50px&quot;}&gt;
&lt;Header /&gt;
{children}
&lt;Footer /&gt;
&lt;/Grid&gt;
&lt;/Container&gt;
&lt;/Providers&gt;
&lt;/body&gt;
&lt;/html&gt;
);
}

What I'm trying to solve: The issue I'm facing is related to the fetch() call I'm making in the file app/api/auth/[...nextauth]/route.ts. Basically, at this point, I receive the following error:

> TypeError: fetch failed
> at Object.fetch (node:internal/deps/undici/undici:11457:11)
> at process.processTicksAndRejections (node:internal/process/task_queues:95:5) { cause: Error: connect
> ECONNREFUSED 127.0.0.1:3001
> at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1494:16)
> at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) {
> errno: -111,
> code: 'ECONNREFUSED',
> syscall: 'connect',
> address: '127.0.0.1',
> port: 3001 } }

I have tried making the same fetch call directly from app/login/page.tsx in the handleSignIn() method, and I obtained the token without any problems. This means that my API has nothing to do with this issue or any problems with the ports. But for some reason, when it goes to the Next-Auth definition, I get the error.

Sharing my docker compose file, just in case:

version: &quot;3.8&quot;
services:
pg-db:
image: postgres:15.1
restart: always
container_name: postgres
ports:
- &quot;5432:5432&quot;
nest-api:
...
depends_on:
- pg-db
env_file: ./.env
ports:
- &quot;3001:3001&quot;
stdin_open: true
tty: true
networks:
- default
nextjs-web:
...
ports:
- &quot;3000:3000&quot;
env_file: ./.env
volumes:
pg-db:

答案1

得分: 1

更改URL从http://localhost:3001http://127.0.0.1:3001为:

http://nest-api:3001

对我解决了问题。在我的Docker Compose配置中,我有一个名为nest-api的API服务。通过在URL中使用服务名称作为主机名,Docker的桥接网络能够自动将名称解析为相应的容器IP地址。这允许在Docker Compose网络中成功进行Next.js应用程序和Nest.js API服务之间的通信。

如果有人想更好地解释,请随时这样做。

英文:

Not really sure why but changing the URL from http://localhost:3001 or http://127.0.0.1:3001 to:

http://nest-api:3001

Resolved the issue for me. In my Docker Compose configuration, I have the API service named nest-api. By using the service name as the hostname in the URL, Docker's bridge network is able to resolve the name to the corresponding container IP address automatically. This allows successful communication between the Next.js application and the Nest.js API service within the Docker Compose network.

If anyone wants to explain better, feel free to do so.

huangapple
  • 本文由 发表于 2023年7月14日 01:43:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/76682020.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定