英文:
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 "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 };
File: app/login/page.tsx
:
"use client";
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),
},
};
}
File: app/providers.tsx
:
"use client";
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>
);
}
File: app/layout.tsx
:
"use client";
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>
);
}
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: "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:
答案1
得分: 1
更改URL从http://localhost:3001
或http://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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论