5

SOLVED!!:

Turns out using the beta turbopack causes this bug to happen. I have opened an issue we'll see when they resolve this.

Question:

I recently started working on a project in NextJS 13 with the new app directory. I implemented Prisma and connected my MySQL DB then I installed next-auth. The user creation and JWT security all works great but when log in and go to a route that is protected by my middleware exported from the next-auth directory, the browser starts looping an infinite amount of GET requests (see image below) and makes the site irresponsive. It will also return the following error back to me:

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

Note:

Removing the middleware.ts file obviously removes the route protection but it also eliminates the GET loop.

If there is anymore data I can supply to assist please let me know.

image:

Get requests

Code:

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: "[email protected]" },
            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;
    }
}

What I tried

I tried the following with no success. They all resulted in the same problem.

  • Different version of different libraries e.g. downgrading next js / downgrading next-auth.
  • Removing the custom login route from the route.ts file and using a predefined user.
  • Removing callbacks.
  • Tried using the middleware on other pages then /settings.
  • Reinstalling the node_modules.
3
  • async is known to cause infinite loops github.com/vercel/next.js/issues/51528#issuecomment-1600671255 Commented Jun 27, 2023 at 19:29
  • @ViktorMS, I read the thread and it states that client components should not be marked as async. None of my components meet those two requirements other then maybe my settings/page.tsx which is marked as async and a child of the root layout which has a client side component that wraps all children in a sessionprovider. But that seems a bit far fetched. Commented Jun 27, 2023 at 20:28
  • In my case it was the Supabase Provider instead, and this helped Commented Aug 30, 2023 at 4:47

1 Answer 1

2

Don't use the turbopack beta with --turbo in the package.json.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.