NextJS 13,在使用Next-Auth中间件时陷入无限的GET请求循环中。无法获取RSC有效载荷。

4

问题解决:

原来使用beta turbopack会导致这个bug发生。我已经提出了一个问题,我们将看到他们何时解决这个问题。

问题:

我最近开始在NextJS 13中使用新的app目录进行项目开发。我实现了Prisma并连接了我的MySQL数据库,然后安装了next-auth。用户创建和JWT安全性都很好,但是当登录并访问由我从next-auth目录导出的中间件保护的路由时,浏览器开始无限循环发送GET请求(见下图),导致网站无响应。同时,它还会返回以下错误信息给我:

"Failed to fetch RSC payload. Falling back to browser navigation. TypeError: NetworkError when attempting to fetch resource."

注意:

删除middleware.ts文件显然会移除路由保护,但也会消除GET循环。

如果有更多数据可以提供以帮助解决问题,请告诉我。

图片:

Get requests

代码:

projectDir\app\api\auth[...nextauth]\route.ts

// Imports
import NextAuth from "next-auth/next";
import prisma from "@/lib/prisma";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import CredentialsProvider from "next-auth/providers/credentials";
import { type NextAuthOptions } from "next-auth";

// NextAuth handler.
export const authOptions: NextAuthOptions = {
    // Set adapter.
    adapter: PrismaAdapter(prisma),
    // Set secret.
    secret: process.env.NEXTAUTH_SECRET,
    // Set session strategy.
    session: {
        strategy: 'jwt'
    },
    // Set different login providers.
    providers:[  
        CredentialsProvider({
            // The name to display on the sign in form (e.g. "Sign in with...")
            name: "Credentials",
            // `credentials` is used to generate a form on the sign in page.
            // You can specify which fields should be submitted, by adding keys to the `credentials` object.
            // e.g. domain, username, password, 2FA token, etc.
            // You can pass any HTML attribute to the <input> tag through the object.
            credentials: {
            username: { label: "Email", type: "text", placeholder: "user@email.com" },
            password: { label: "Password", type: "password" }
            },
            async authorize(credentials) {
                // Create request to login api
                const res = await fetch("http://localhost:3000/api/login", {
                    method: "POST",
                    headers: {
                        "Content-Type": "application/json",
                    },
                    body: JSON.stringify({
                        username: credentials?.username,
                        password: credentials?.password,
                    }),
                });

                // Get response from request
                const user = await res.json();
            
                if (res.ok && user) {
                    // If request returns an user, return the user object received.
                    return user
                } else {
                    // If request does not return an user, return null.
                    return null
            
                    // You can also Reject this callback with an Error thus the user will be sent to the error page with the error message as a query parameter
                }
            }
        })
    ],
    callbacks: {
        // Callback for when a jwt is created or updated.
        async jwt({token, user}) {

            return({...token,...user});
        },

        // Callback for when a session is checked.
        async session({session, token}) {
            // Add token to session.
            session.user = token as any;

            return session;
        }
    }
}

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST}

projectDir\middleware.ts

export { default } from 'next-auth/middleware'

export const config = {
    matcher: "/settings"
}

projectDir\app\api\login\route.ts

// Imports
import { signJwtAccessToken } from "@/lib/jwt";
import prisma from "@/lib/prisma";
import * as bcrypt from 'bcryptjs';

// Interface
interface RequestBody {
    username: string;
    password: string;
}

// Return route.
export async function POST(request: Request) {
    // Get requests body.
    const body: RequestBody = await request.json();

    // Create const with requested user.
    const user = await prisma.user.findFirst({
        where: {
            email: body.username,
        }
    });

    // If user exists check if password is correct and return the user.
    if(user && ( await bcrypt.compare(body.password, user.password))) {
        // Remove password from user object in the response.
        const {password, ...userWithoutPass} = user

        // Create jwt.
        const accessToken = signJwtAccessToken(userWithoutPass);

        // Combine user with jwt as result.
        const result = {
            ...userWithoutPass,
            accessToken,
        }

        // Return the result as JSON object.
        return new Response(JSON.stringify(result));
    }

    // Return null as JSON object.
    else return new Response(JSON.stringify(null));
}

projectDir\app\lib\jwt.ts


// Imports
import jwt,{ JwtPayload } from "jsonwebtoken";

// Interfaces
interface SignOption {
    expiresIn?: string | number,
}

// Default token expiration date.
const DEFAULT_SIGN_OPTION:SignOption={
    expiresIn: "1h"
}

// Function to create jwt.
export function signJwtAccessToken(payload: JwtPayload, options: SignOption= DEFAULT_SIGN_OPTION) {
    // Get secret key.
    const secret_key = process.env.SECRET_KEY;

    // Create token.
    const token = jwt.sign(payload, secret_key!, options);

    // Return the token.
    return token;
}

// Function to verify jwt.
export function verifyJwt(token: string) {
    try {
        // Get secret key.
        const secret_key = process.env.SECRET_KEY; 
        // Verify secret key.
        const decoded = jwt.verify(token, secret_key!);

        // Return if jwt is valid 
        return decoded as JwtPayload;
    } catch (error) {
        // If jwt is not valid, log the error.
        console.log(error);

        // And return null.
        return null;
    }
}

我尝试过的方法

我尝试了以下方法,但都没有成功。它们都导致了同样的问题。

  • 尝试使用不同版本的库,例如降级 next js / 降级 next-auth。
  • 从 route.ts 文件中删除自定义登录路由,并使用预定义的用户。
  • 删除回调函数。
  • 尝试在除了 /settings 之外的其他页面上使用中间件。
  • 重新安装 node_modules。

异步操作已知会导致无限循环。请参考此链接:https://github.com/vercel/next.js/issues/51528#issuecomment-1600671255 - ViktorMS
async被认为会导致无限循环。https://github.com/vercel/next.js/issues/51528#issuecomment-1600671255 - undefined
@ViktorMS,我读了这个帖子,上面说客户端组件不应该标记为async。除了我的settings/page.tsx可能被标记为async并且是根布局的子组件之外,我的其他组件都不满足这两个要求。根布局有一个客户端组件,它在sessionprovider中包装了所有子组件。但这似乎有点牵强。 - TheToughest
@ViktorMS,我读了这个讨论,上面说客户端组件不应该标记为async。除了我的settings/page.tsx可能被标记为async并且是根布局的子组件之外,我的其他组件都不满足这两个要求。根布局有一个客户端组件,它在sessionprovider中包裹了所有子组件。但这似乎有点牵强。 - undefined
在我的情况下,我使用的是Supabase提供者,而这个链接对我有所帮助。 - wscourge
1个回答

0
不要在package.json中使用turbopack beta和--turbo。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接