next-auth icon indicating copy to clipboard operation
next-auth copied to clipboard

Auth.js example Nextjs site breaks when integrating @auth/mongodb-adapter from example code

Open FrankTrog opened this issue 1 year ago • 1 comments

Adapter type

@auth/mongodb-adapter

Environment

System: OS: Windows 10 10.0.19045 CPU: (16) x64 AMD Ryzen 7 4800H with Radeon Graphics Memory: 7.99 GB / 31.42 GB Browsers: Edge: Chromium (121.0.2277.112) Internet Explorer: 11.0.19041.3636 npmPackages: @auth/mongodb-adapter: ^2.4.0 => 2.4.0 next: latest => 14.1.0 next-auth: beta => 5.0.0-beta.11 react: ^18.2.0 => 18.2.0

Reproduction URL

https://github.com/FrankTrog/next-auth/tree/main/apps/examples/nextjs

Describe the issue

When following the instructions from Auth.js to install the @auth/mongodb-adapter in order to use MongoDB for session storage many errors are thrown by the middleware in the terminal regarding mongodb. It appears there are modules required by mongodb that cannot be resolved. Also, there is an error about the edge runtime not supporting Node.js 'stream' module. It is surprising to me that there are so many errors like this from following official documentation and using the example respository directly from Auth.js.

Mongodb adapter documentation referenced

Missing modules

  • kerberos
  • @mongodb-js/zstd
  • @aws-sdk/credential-providers
  • gcp-metadata
  • snappy
  • socks
  • aws4
  • mongodb-client-encryption

Terminal output ` npm run dev

dev next

▲ Next.js 14.1.0

  • Local: http://localhost:3000
  • Environments: .env.local

✓ Ready in 1912ms ○ Compiling /middleware ... ⚠ ./node_modules/mongodb/lib/deps.js Module not found: Can't resolve 'kerberos' in 'next-auth\apps\examples\nextjs\node_modules\mongodb\lib'

Import trace for requested module: ./node_modules/mongodb/lib/deps.js ./node_modules/mongodb/lib/client-side-encryption/client_encryption.js ./node_modules/mongodb/lib/index.js ./components/mdbconn.tsx ./auth.ts

./node_modules/mongodb/lib/deps.js Module not found: Can't resolve '@mongodb-js/zstd' in 'next-auth\apps\examples\nextjs\node_modules\mongodb\lib'

Import trace for requested module: ./node_modules/mongodb/lib/deps.js ./node_modules/mongodb/lib/client-side-encryption/client_encryption.js ./node_modules/mongodb/lib/index.js ./components/mdbconn.tsx ./auth.ts

./node_modules/mongodb/lib/deps.js Module not found: Can't resolve '@aws-sdk/credential-providers' in 'next-auth\apps\examples\nextjs\node_modules\mongodb\lib'

Import trace for requested module: ./node_modules/mongodb/lib/deps.js ./node_modules/mongodb/lib/client-side-encryption/client_encryption.js ./node_modules/mongodb/lib/index.js ./components/mdbconn.tsx ./auth.ts

./node_modules/mongodb/lib/deps.js Module not found: Can't resolve 'gcp-metadata' in 'next-auth\apps\examples\nextjs\node_modules\mongodb\lib'

Import trace for requested module: ./node_modules/mongodb/lib/deps.js ./node_modules/mongodb/lib/client-side-encryption/client_encryption.js ./node_modules/mongodb/lib/index.js ./components/mdbconn.tsx ./auth.ts

./node_modules/mongodb/lib/deps.js Module not found: Can't resolve 'snappy' in 'next-auth\apps\examples\nextjs\node_modules\mongodb\lib'

Import trace for requested module: ./node_modules/mongodb/lib/deps.js ./node_modules/mongodb/lib/client-side-encryption/client_encryption.js ./node_modules/mongodb/lib/index.js ./components/mdbconn.tsx ./auth.ts

./node_modules/mongodb/lib/deps.js Module not found: Can't resolve 'socks' in 'next-auth\apps\examples\nextjs\node_modules\mongodb\lib'

Import trace for requested module: ./node_modules/mongodb/lib/deps.js ./node_modules/mongodb/lib/client-side-encryption/client_encryption.js ./node_modules/mongodb/lib/index.js ./components/mdbconn.tsx ./auth.ts

./node_modules/mongodb/lib/deps.js Module not found: Can't resolve 'aws4' in 'next-auth\apps\examples\nextjs\node_modules\mongodb\lib'

Import trace for requested module: ./node_modules/mongodb/lib/deps.js ./node_modules/mongodb/lib/client-side-encryption/client_encryption.js ./node_modules/mongodb/lib/index.js ./components/mdbconn.tsx ./auth.ts

./node_modules/mongodb/lib/deps.js Module not found: Can't resolve 'mongodb-client-encryption' in 'next-auth\apps\examples\nextjs\node_modules\mongodb\lib'

Import trace for requested module: ./node_modules/mongodb/lib/deps.js ./node_modules/mongodb/lib/client-side-encryption/client_encryption.js ./node_modules/mongodb/lib/index.js ./components/mdbconn.tsx ./auth.ts ⨯ Error: The edge runtime does not support Node.js 'stream' module. Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime at (webpack-internal:///(middleware)/./node_modules/next/dist/esm/server/web/globals.js:33) at Object.get (webpack-internal:///(middleware)/./node_modules/next/dist/esm/server/web/globals.js:33:19) at eval (webpack-internal:///(middleware)/./node_modules/mongodb/lib/cursor/abstract_cursor.js:617:45) at (middleware)/./node_modules/mongodb/lib/cursor/abstract_cursor.js (file://next-auth\apps\examples\nextjs.next\server\middleware.js:899:1) at webpack_require (file://next-auth\apps\examples\nextjs.next\server\edge-runtime-webpack.js:37:33) at fn (file://next-auth\apps\examples\nextjs.next\server\edge-runtime-webpack.js:280:21) at eval (webpack-internal:///(middleware)/./node_modules/mongodb/lib/cursor/aggregation_cursor.js:9:27) at (middleware)/./node_modules/mongodb/lib/cursor/aggregation_cursor.js (file://next-auth\apps\examples\nextjs.next\server\middleware.js:910:1) at webpack_require (file://next-auth\apps\examples\nextjs.next\server\edge-runtime-webpack.js:37:33) at fn (file://next-auth\apps\examples\nextjs.next\server\edge-runtime-webpack.js:280:21) at eval (webpack-internal:///(middleware)/./node_modules/mongodb/lib/collection.js:10:30) { middleware: true } `

localhost:3000 Screenshot_27

I am most of the way through converting a project and stuck at this point. Guidance would be fantastic. Thanks!

How to reproduce

Expected behavior

Example site built with Auth.js should be usable without errors triggered by MongoDB.

FrankTrog avatar Feb 16 '24 22:02 FrankTrog

This may be similar to #42277 and #42313 which were supposedly resolved in v13.0.2-canary.o of Next.js. That issue was closed though and this issue is regarding the latest version of Authjs and Nextjs.

FrankTrog avatar Feb 16 '24 22:02 FrankTrog

I am having the same issue with [email protected], [email protected] and @auth/[email protected]. Adding mongodb to serverComponentsExternalPackages didn't solve the problem.

dsantos avatar Feb 22 '24 20:02 dsantos

I'm having the same issue. Google and GitHub providers works, but credentials don't.

Error happens when i set the following code line in auth.config.ts, at the credentials provider authorize() function

const user = await getUserByEmail(email);

Environment

Local mongodb Windows 11 Home compilation 22631.3155 node v20.9.0

Error msgs

msg on Google Chrome v 122.0.6261.71: Server Error Error: The edge runtime does not support Node.js 'stream' module. Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime

msg on terminal: Error: The edge runtime does not support Node.js 'stream' module. Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime at (webpack-internal:///(middleware)/./node_modules/next/dist/esm/server/web/globals.js:33) at Object.get (webpack-internal:///(middleware)/./node_modules/next/dist/esm/server/web/globals.js:33:19) at eval (webpack-internal:///(middleware)/./node_modules/mongodb/lib/cursor/abstract_cursor.js:617:45) at (middleware)/./node_modules/mongodb/lib/cursor/abstract_cursor.js (file://C:\Users\rafae\Documents\tutorials\next-project.next\server\middleware.js:919:1) at webpack_require (file://C:\Users\rafae\Documents\tutorials\next-project.next\server\edge-runtime-webpack.js:37:33) at fn (file://C:\Users\rafae\Documents\tutorials\next-project.next\server\edge-runtime-webpack.js:280:21) at eval (webpack-internal:///(middleware)/./node_modules/mongodb/lib/cursor/aggregation_cursor.js:9:27) at (middleware)/./node_modules/mongodb/lib/cursor/aggregation_cursor.js (file://C:\Users\rafae\Documents\tutorials\next-project.next\server\middleware.js:930:1) at webpack_require (file://C:\Users\rafae\Documents\tutorials\next-project.next\server\edge-runtime-webpack.js:37:33) at fn (file://C:\Users\rafae\Documents\tutorials\next-project.next\server\edge-runtime-webpack.js:280:21) at eval (webpack-internal:///(middleware)/./node_modules/mongodb/lib/collection.js:10:30) { middleware: true }

auth.config.ts

Error happens in authorize(), when consulting db for a user email

import type { NextAuthConfig } from "next-auth";
import Credentials from "next-auth/providers/credentials";
import Github from "next-auth/providers/github";
import Google from "next-auth/providers/google";
import bcrypt from "bcryptjs";

import { LoginSchema } from "@/schemas";
import { getUserByEmail } from "@/data/user";


export default {
    providers: [
        Google,
        Github,
        Credentials({
            async authorize(credentials) {
                const validatedFields = LoginSchema.safeParse(credentials);

                if (validatedFields.success) {

                    const { email, password } = validatedFields.data;

                   const user = await getUserByEmail(email);
                    if (!user || !user.password) return null;

                    const passwordsMatch = await bcrypt.compare(
                        password,
                        user.password,
                    );

                    if (passwordsMatch) {
                        return user;
                    }

                }

                return null;
            }
        })


    ],
    
} satisfies NextAuthConfig;

Here is the getUserByEmail():

import clientPromise from "@/lib/db";

export const getUserByEmail = async (email: string) => {
    try {

        // Connect to the database using the clientPromise
        const client = await clientPromise;
        const db = client.db("myDatabaseName"); 

        // Try to find an existing user by email
        const user = await db.collection("users").findOne({ email });

        return user;
    } catch {
        return null;
    }
};

auth.js

strategy is set to jwt, but it seems it keep trying to use database on edge.

import NextAuth from "next-auth"
import authConfig from "@/auth.config";
import { MongoDBAdapter } from "@auth/mongodb-adapter"
import clientPromise from "@/lib/db"

export const {
    handlers: { GET, POST },
    auth,
    signIn,
    signOut, } = NextAuth({

      adapter: MongoDBAdapter(clientPromise),
      session: {strategy: "jwt"},
      ...authConfig,

        pages: {
            signIn: '/auth/login',
            signOut: '/auth/login',
            error: '/auth/error',
        },

        callbacks: {
        },

        

    })

middleware.ts

import NextAuth from "next-auth";
import authConfig from "@/auth.config";
import {
  DEFAULT_LOGIN_REDIRECT,
  apiAuthPrefix,
  authRoutes,
  publicRoutes,
} from "@/routes";


const { auth } = NextAuth(authConfig);

export default auth((req) => {
  const { nextUrl } = req;
  const isLoggedIn = !!req.auth;

  const isApiAuthRoute = nextUrl.pathname.startsWith(apiAuthPrefix);
  const isPublicRoute = publicRoutes.includes(nextUrl.pathname);
  const isAuthRoute = authRoutes.includes(nextUrl.pathname);

  if (isApiAuthRoute) {
    return;
  }

  if (isAuthRoute) {
    if (isLoggedIn) {
      return Response.redirect(new URL(DEFAULT_LOGIN_REDIRECT, nextUrl))
    }
    return;
  }

  if (!isLoggedIn && !isPublicRoute) {

    return Response.redirect(new URL("auth/login", nextUrl))
  }

  return;
})

// Optionally, don't invoke Middleware on some paths
export const config = {
  matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'],
}

db.ts

// This approach is taken from https://github.com/vercel/next.js/tree/canary/examples/with-mongodb
import { MongoClient } from "mongodb"

declare global {
    var _mongoClientPromise: Promise<MongoClient>;
  }

if (!process.env.MONGODB_URI) {
  throw new Error('Invalid/Missing environment variable: "MONGODB_URI"')
}

const uri = process.env.MONGODB_URI
const options = {}

let client
let clientPromise: Promise<MongoClient>



if (process.env.NODE_ENV === "development") {
    // In development mode, use a global variable so that the value
    // is preserved across module reloads caused by HMR (Hot Module Replacement).
    if (!global._mongoClientPromise) {
        client = new MongoClient(uri, options)
        global._mongoClientPromise = client.connect()
    }
    clientPromise = global._mongoClientPromise
} else {
    // In production mode, it's best to not use a global variable.
    client = new MongoClient(uri, options)
    clientPromise = client.connect()
}

// Export a module-scoped MongoClient promise. By doing this in a
// separate module, the client can be shared across functions.
export default clientPromise

dependencies

{
  "name": "next-project",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@auth/mongodb-adapter": "^2.4.0",
    "bcryptjs": "^2.4.3",
    "mongodb": "^6.3.0",
    "next": "14.1.0",
    "next-auth": "^5.0.0-beta.13",
    "next-themes": "^0.2.1",
    "nodemailer": "^6.9.10",
    "react": "^18",
    "react-day-picker": "^8.10.0",
    "react-dom": "^18",
    "react-hook-form": "^7.50.1",
    "zod": "^3.22.4"
  },
  "devDependencies": {
    "@types/bcryptjs": "^2.4.6",
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "autoprefixer": "^10.0.1",
    "eslint": "^8",
    "eslint-config-next": "14.1.0",
    "postcss": "^8",
    "tailwindcss": "^3.3.0",
    "typescript": "^5"
  }
}

rgno1 avatar Feb 29 '24 16:02 rgno1

Hey @rgno1, did you find a solution?

ann0nip avatar Mar 16 '24 06:03 ann0nip

@ann0nip were you able to find the solution?

Azania-Mokhampane avatar Mar 22 '24 09:03 Azania-Mokhampane

@rgno1 did you find the solution. I am also facing the same problem.

shagunsharma6677 avatar Mar 31 '24 05:03 shagunsharma6677

this is an issue with MongoDB not being fully edge compatible, sorry that the doc is not super clear about this. Here's my suggestion - this works for Prisma before they added the Edge runtime support recently:

Define the auth.config.ts file:

export default {
  providers: [
    GitHub,
    Google,
    Facebook,
    Twitter,
    //.. other providers
  ],
} satisfies NextAuthConfig

Import and use the MongoDBAdapter in the auth.ts file:

import NextAuth from "next-auth"
import authConfig from "auth.config"
import { MongoDBAdapter } from "@auth/mongodb-adapter"

export const { handlers, auth, signIn, signOut, unstable_update } = NextAuth({
  adapter: PrismaAdapter(globalThis.prisma),
  session: { strategy: "jwt" },
  ...authConfig,
})

edit: Note that it's important to use the JWT strategy here to avoid the DB call in the middleware.

In the middleware file, import the auth.config and use it as the configuration for NextAuth (note that the adapter is not used here to avoid the error):

import NextAuth from "next-auth"
import authConfig from "auth.config"

export const middleware = NextAuth(authConfig).auth

Let me know if this helps. Close as the error is not from next-auth or @auth/mongodb-adapter

ThangHuuVu avatar Mar 31 '24 14:03 ThangHuuVu

@ThangHuuVu Hey, would be really helpful if you can guide me how to use this strategy in Client side components to signIn for example. I am getting requestAsyncStorage not available.

grolox69 avatar Apr 06 '24 23:04 grolox69

@ThangHuuVu Hey, would be really helpful if you can guide me how to use this strategy in Client side components to signIn for example. I am getting requestAsyncStorage not available.

Please open another issue with a minimal reproduction, 🙏 thank you

ThangHuuVu avatar Apr 09 '24 16:04 ThangHuuVu

this is an issue with MongoDB not being fully edge compatible, sorry that the doc is not super clear about this. Here's my suggestion - this works for Prisma before they added the Edge runtime support recently:

Define the auth.config.ts file:

export default {
  providers: [
    GitHub,
    Google,
    Facebook,
    Twitter,
    //.. other providers
  ],
} satisfies NextAuthConfig

Import and use the MongoDBAdapter in the auth.ts file:

import NextAuth from "next-auth"
import authConfig from "auth.config"
import { MongoDBAdapter } from "@auth/mongodb-adapter"

export const { handlers, auth, signIn, signOut, unstable_update } = NextAuth({
  adapter: PrismaAdapter(globalThis.prisma),
  session: { strategy: "jwt" },
  ...authConfig,
})

In the middleware file, import the auth.config and use it as the configuration for NextAuth (note that the adapter is not used here to avoid the error):

import NextAuth from "next-auth"
import authConfig from "auth.config"

export const middleware = NextAuth(authConfig).auth

Let me know if this helps. Close as the error is not from next-auth or @auth/mongodb-adapter

Can you elaborate a bit more on the middleware part? I followed your instructions (as well as the page on this in the docs), but I'm unsure how to then reference the database user without having access to the database adapter in the middleware.

import { NextResponse } from "next/server";
import authConfig from "./auth.config";
import NextAuth from "next-auth";

const { auth } = NextAuth(authConfig);

export default auth((req) => {
  const url = req.nextUrl.clone();
  url.pathname = "/";
  if (req.auth?.user?.role == "admin") {
    console.log("User: " + JSON.stringify(req.auth.user));
  }
});

export const config = {
  matcher: ["/api/:path*"],
};

This results in all calls in the app throwing this error:

[auth][error] MissingAdapter: Database session requires an adapter.. Read more at https://errors.authjs.dev#missingadapter at assertConfig (webpack-internal:///(middleware)/./node_modules/@auth/core/lib/utils/assert.js:146:24) at Auth (webpack-internal:///(middleware)/./node_modules/@auth/core/index.js:88:95)

This is for MongoDB Adapter

IlyyA avatar May 05 '24 07:05 IlyyA

@IlyyA the keys are:

  1. Use the JWT strategy
  2. Don’t import the adapter into the middleware

This can work because we only use the middleware for checking the session in the auth method. Let me know if this helps!

ThangHuuVu avatar May 05 '24 10:05 ThangHuuVu

Hey @rgno1, did you find a solution?

I opted not to use Prisma in my project, so I resolved the issue by implementing an API route, which worked. Using prisma also work.

auth.config.ts

export default {
  providers: [
    Google,
    //Github,
    Credentials({
      async authorize(credentials) {

        const validatedFields = LoginSchema.safeParse(credentials);

        if (validatedFields.success) {
          const { email, password } = validatedFields.data;

          const response = await fetch(`http://localhost:${process.env.PORT}/api/users`, {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              email,
              password
            })
          })

          const user = await response.json()

          if (response.ok && user) {
            return user
          }

          return null;
        }

        return null;

      }

    })


  ],

} satisfies NextAuthConfig;

/api/users end point

import bcrypt from 'bcryptjs';
import { getUserByEmail } from '@/data/user';
import { NextRequest, NextResponse } from 'next/server';


export async function POST(req: NextRequest) {
  try {
    // Parsing JSON body in Edge Middleware
    const body = await req.json();
    const { email, password } = body;

    // Basic validation
    if (!email || !password) {
      return new NextResponse(JSON.stringify({ message: 'Email and password are required' }), {
        status: 400,
        headers: {
          "Content-Type": "application/json",
        },
      });
    }

    // Simulated getUserByEmail and bcrypt.compare (must be compatible with Edge runtime)
    const user = await getUserByEmail(email); // This function needs to be compatible with Edge runtime

    if (!user || !user.password) {
      // User not found
      return new NextResponse(JSON.stringify({ message: 'Invalid credentials' }), {
        status: 401,
        headers: {
       "Content-Type": "application/json",
        },
      });
    }


    // Check if the password is correct
    const isMatch = await bcrypt.compare(password, user.password); // Simulated, needs to be Edge-compatible
    if (!isMatch) {
      // Password does not match
      return new NextResponse(JSON.stringify({ message: 'Invalid credentials' }), {
        status: 401,
        headers: {
          "Content-Type": "application/json",
        },
      });
    }

    // Successfully authenticated
    const { password: _, ...userWithoutPassword } = user; // Remove the password from the user object before sending
    return new NextResponse(JSON.stringify(userWithoutPassword), {
      status: 200,
      headers: {
        "Content-Type": "application/json",
      },
    });
  } catch (error) {
    console.error(error);
    return new NextResponse(JSON.stringify({ message: 'Internal server error' }), {
      status: 500,
      headers: {
        "Content-Type": "application/json",
      },
    });
  }
}

rgno1 avatar May 06 '24 16:05 rgno1

@IlyyA the keys are:

  1. Use the JWT strategy
  2. Don’t import the adapter into the middleware

This can work because we only use the middleware for checking the session in the auth method. Let me know if this helps!

So using the database strategy is not possible along with any middleware auth, as it is not possible to get the Adapter/req.auth.user object in the middleware without throwing the error?

IlyyA avatar May 06 '24 17:05 IlyyA

In my case, I am using email provider, so I had to do this in the middleware to avoid issues with the auth config validation:

import NextAuth from "next-auth"
import authConfig from "./auth.config"

export const middleware = NextAuth({
  ...authConfig,
  adapter: {
    createVerificationToken: () => { console.log('middleware createVerificationToken')},
    useVerificationToken: () => { console.log('middleware useVerificationToken')},
    getUserByEmail: () => { console.log('middleware getUserByEmail')}
  } as unknown
}).auth

Otherwise it would say:

[auth][error] MissingAdapter: Email login requires an adapter.. Read more at https://errors.authjs.dev#missingadapter

StasDeep avatar Jun 09 '24 11:06 StasDeep