nextauthjs / next-auth

Authentication for the Web.
https://authjs.dev
ISC License
25.01k stars 3.53k forks source link

EDGE_FUNCTION_INVOCATION_TIMEOUT when using DrizzleAdapter | timeout Vercel edge functions #10773

Open tobiasmeyhoefer opened 6 months ago

tobiasmeyhoefer commented 6 months ago

Environment

Next Auth v5, Vercel, Neon, Drizzle

Reproduction URL

https://github.com/tobiasmeyhoefer/bitz

Describe the issue

In localhost everything works fine but when deployed with Vercel hitting this error every second or third request when the middleware gets called...

long waiting and then this example error message: [GET] [middleware: "middleware"] /browse reason=EDGE_FUNCTION_INVOCATION_TIMEOUT, status=504, user_error=true

I already tried different package versions and setting location near me

How to reproduce

you can clone and try to deploy it yourself or look here: https://bitz-ecru.vercel.app

Im using neon serverless database... one thing that is sus to me is that the file db.ts:

gets called every time, shouldn't the connection be established only once?

Expected behavior

no errors at all

wrod7 commented 6 months ago

having the same issue using prisma neon adaptor

rob7thousand commented 6 months ago

having the same issue with planetscale adapter

kisankumavat85 commented 6 months ago

I am having the same issue, I am using App Router (v14), Prisma, Neon and Next-Auth v5.

Muhammad-RK-Isa commented 6 months ago

I'm having the same issue while implementing Role Based authentication using Next-auth v5. Everything works fine in development but edge function gets timed out in production.

Note: It's not a Vercel issue as I've tried Netlify too and got the same result. Having this issue for two weeks now.

Next-auth v4 doesn't have this kinda issues. But as I want to implement auth using v5, I've tried multiple ways of fixing this issue and nothing seems to be working :)

ndom91 commented 6 months ago

Depending on your setup, you may be running into issues where your adapter is not "edge compatible" and is timing out trying to start it up, etc. Please check out our edge compatibility guide.

If you've confirmed your adapter is "edge compatible", then you may also be doing too many DB actions in your middleware. The limit there seems to be "25s to begin returning data".

That's an awfully long time though, so unless yuo're doing something extra heavy you shouldn't be hitting that limit without other issues I'd guess.

Please provide some more details, like versions of all the relevant packages and some log output and we can try and figure out what's going on :pray:

rob7thousand commented 6 months ago

This is the only log I have in production: [GET] [middleware: "middleware"] /dashboard/company reason=EDGE_FUNCTION_INVOCATION_TIMEOUT, status=504, user_error=true

Versions:

"@auth/prisma-adapter": "^2.0.0",
"next-auth": "5.0.0-beta.17",'

Here is my db instance:

import { Client } from "@planetscale/database"
import { PrismaPlanetScale } from "@prisma/adapter-planetscale"
import { PrismaClient } from "@prisma/client"

import { env } from "@/env.mjs"

const connectionString = `${env.DATABASE_URL}`

const client = new Client({ url: connectionString })
const adapter = new PrismaPlanetScale(client)
const prisma = new PrismaClient({ adapter })

export const db = prisma

My auth.ts:

import { PrismaAdapter } from "@auth/prisma-adapter"
import NextAuth from "next-auth"
import type { Provider } from "next-auth/providers"
import GitHub from "next-auth/providers/github"
import Google from "next-auth/providers/google"

import { db } from "@/lib/db"

const providers: Provider[] = [GitHub, Google]

export const config = {
  adapter: PrismaAdapter(db),
  providers: providers,
  pages: {
    signIn: "/login",
  },
  callbacks: {
    async session({ token, session }) {
      if (token) {
        session.user.id = token.id
        session.user.name = token.name
        session.user.email = token.email
        session.user.image = token.picture
      }

      return session
    },
    async jwt({ token, user }) {
      const dbUser = await db.user.findFirst({
        where: {
          email: token.email,
        },
      })

      if (!dbUser) {
        if (user) {
          token.id = user?.id
        }
        return token
      }

      return {
        id: dbUser.id,
        name: dbUser.name,
        email: dbUser.email,
        picture: dbUser.image,
      }
    },
  },
}

export const { handlers, auth, signIn, signOut } = NextAuth(config)

And my middleware:

import { NextResponse } from "next/server"
import { auth } from "@/auth"
import {
  DEFUALT_LOGIN_REDIRECT,
  apiAuthPrefix,
  authRoutes,
  publicRoutes,
} from "@/routes/auth"

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 NextResponse.redirect(new URL(DEFUALT_LOGIN_REDIRECT, nextUrl))
    }
    return
  }

  if (!isLoggedIn && !isPublicRoute) {
    return NextResponse.redirect(new URL("/login", nextUrl))
  }

  return
})

export const config = {
  matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"],
}
ndom91 commented 6 months ago

@Yvon-Data can you share the versions of all the relevant prisma packages as well?

rob7thousand commented 6 months ago

Of course!

"prisma": "^5.13.0",
"@prisma/adapter-planetscale": "^5.13.0",
"@prisma/client": "^5.13.0"
ndom91 commented 6 months ago

Of course!

"prisma": "^5.13.0",
"@prisma/adapter-planetscale": "^5.13.0",
"@prisma/client": "^5.13.0"

Hmm okay so it looks like that should be "edge compatible", but your middleware / auth.js setup is relatively simple so I can't imagine its anything else other than the DB lookup in your jwt callback 🤔

Might be worthwhile testing another DB call in an unrelated edge api route, like shown in this prisma + planetscale doc: https://www.prisma.io/docs/orm/prisma-client/deployment/edge/deploy-to-vercel#planetscale, to see if you run into similar errors there or if that works as expected

kisankumavat85 commented 6 months ago

I downgraded next-auth to v4 and now its working like a charm. I was also using edge compatible prisma but the issue is with next-auth v5.

alexcjwei commented 5 months ago

I'm having the same timeout issues. Works locally but not when deployed to Vercel. Here's my information:

Vercel logs: [GET] [middleware: "src/middleware"] /games reason=EDGE_FUNCTION_INVOCATION_TIMEOUT, status=504, user_error=true

auth.ts:

import NextAuth from 'next-auth';
import { PrismaAdapter } from '@auth/prisma-adapter';
import prisma from '@/lib/prisma';
import Google from 'next-auth/providers/google';

export const { handlers, auth, signIn, signOut } = NextAuth({
  adapter: PrismaAdapter(prisma),
  providers: [Google],
});

schema.prisma:

generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["driverAdapters"]
}

datasource db {
  provider  = "postgresql"
  url       = env("POSTGRES_PRISMA_URL") // uses connection pooling
  directUrl = env("POSTGRES_URL_NON_POOLING") // uses a direct connection
}

prisma.ts:

import { PrismaClient } from '@prisma/client';
import { PrismaNeon } from '@prisma/adapter-neon';
import { Pool } from '@neondatabase/serverless';

let prisma: PrismaClient;

declare global {
  var prisma: PrismaClient;
}

const createPrismaClient = () => {
  const neon = new Pool({ connectionString: process.env.POSTGRES_PRISMA_URL });
  const adapter = new PrismaNeon(neon);
  const prisma = new PrismaClient({ adapter });
  return prisma;
};

if (process.env.NODE_ENV === 'production') {
  prisma = createPrismaClient();
} else {
  if (!global.prisma) {
    global.prisma = createPrismaClient();
  }
  prisma = global.prisma;
}
export default prisma;

middleware.ts:

export { auth as middleware } from '@/lib/auth';

package.json:

    "@auth/prisma-adapter": "^2.2.0",
    "@neondatabase/serverless": "^0.9.3",
    "@prisma/adapter-neon": "^5.15.0",
    "next": "14.2.4",
    "next-auth": "^5.0.0-beta.19",
    "prisma": "^5.15.0",

Not sure I need to be instantiating the prisma client this way but kinda just following the docs. Please let me know if there's any other information I can use to resolve this issue. It seems based on the documentation that this setup should be edge compatible.

tobiasmeyhoefer commented 5 months ago

What helps me was securing that drizzle is used in the correct way . Look at this guide: https://orm.drizzle.team/learn/tutorials/drizzle-with-vercel-edge-functions

alexcjwei commented 5 months ago

To fix the middleware timing out I explicitly set the runtime to edge with export const runtime = 'experimental-edge';

Tried doing the same with the api/auth/[...nextauth]/route.ts route by adding export const runtime = 'edge';, but then I get a strange error Error: The Edge Function "api/auth/[...nextauth]" size is 1 MB and your plan size limit is 1 MB. Learn More: https://vercel.link/edge-function-size even though despite this from build:

├ ƒ /api/auth/[...nextauth]              0 B                0 B
tycomo commented 4 months ago

I am having the same issues in production. Everything seems fine during development but once deployed to Vercel approximately 1/5 requests going through middleware will timeout with a similar error:

[GET] [middleware: "src/middleware"] /en/app/settings reason=INTERNAL_EDGE_FUNCTION_INVOCATION_FAILED, status=500, upstream_status=500, user_error=false

package.json

  "@auth/prisma-adapter": "^2.4.1",
  "@prisma/client": "^5.16.1",
  "@prisma/extension-accelerate": "^1.1.0",
  "prisma": "^5.16.1",
  "next": "^14.2.4",
  "next-auth": "5.0.0-beta.19"

db.ts

import { PrismaClient } from "@prisma/client/edge";
import { withAccelerate } from '@prisma/extension-accelerate'

import { env } from "~/env";

const createPrismaClient = () =>
  new PrismaClient({
    log:
      env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
  }).$extends(withAccelerate());

const globalForPrisma = globalThis as unknown as {
  prisma: ReturnType<typeof createPrismaClient> | undefined;
};

export const db = globalForPrisma.prisma ?? createPrismaClient();

if (env.NODE_ENV !== "production") globalForPrisma.prisma = db;

middleware.ts

import { auth } from "./server/auth";
import { NextResponse } from "next/server";

export default auth((req) => {
  //redirect to sign in page if not authenticated
  if (!req.auth && req.nextUrl.pathname !== "/auth/signin") {
    const newUrl = new URL("/auth/signin", req.nextUrl.origin)
    return Response.redirect(newUrl)
  }
});

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

I tried adding export const runtime = 'experimental-edge'; as mentioned above but this did not make any difference for me.

mattgraphlan commented 4 months ago

One thing that helped me was getting rid of the neon websocket adapter. I had:

import ws from "ws";
if (typeof WebSocket === "undefined") {
  neonConfig.webSocketConstructor = ws;
}
const connectionString = process.env.DATABASE_URL;

const pool = new Pool({ connectionString });
const adapter = new PrismaNeon(pool);
export const prisma = new PrismaClient({ adapter });

Instead, I switched it to a normal pool, and I stopped getting the timeouts.

const neon = new Pool({
  connectionString: process.env.DATABASE_URL,
});
const adapter = new PrismaNeon(neon);
export const prisma = new PrismaClient({ adapter });
jkosoy commented 3 months ago

Had a lot of problems with this exact setup. I finally got it working, here's what I have.

auth.ts

import { drizzle } from "drizzle-orm/neon-http";
import { neon } from "@neondatabase/serverless";
import NextAuth from "next-auth"
import { DrizzleAdapter } from "@auth/drizzle-adapter"

const { 
    DATABASE_URL
} = process.env

const databaseConnection = () => {
    const sql = neon(DATABASE_URL!);
    const db = drizzle(sql);    
    return db;
}

const drizzleAdapter = () => {
    const db = databaseConnection();
    return DrizzleAdapter(db)
}

export const { auth, handlers, signIn, signOut } = NextAuth({
    adapter: drizzleAdapter(),
    providers: [ ... ]
})

app/api/[...nextauth]/route.ts

import { handlers } from "@/auth"
import { NextRequest } from "next/server"

// more info here: https://github.com/nextauthjs/next-auth/issues/10928#issuecomment-2162893683
const reqWithTrustedOrigin = (req: NextRequest): NextRequest => {
    if (process.env.AUTH_TRUST_HOST !== 'true') return req
    const proto = req.headers.get('x-forwarded-proto')
    const host = req.headers.get('x-forwarded-host')
    if (!proto || !host) {
        console.warn("Missing x-forwarded-proto or x-forwarded-host headers.")
        return req
    }
    const envOrigin = `${proto}://${host}`
    const { href, origin } = req.nextUrl
    return new NextRequest(href.replace(origin, envOrigin), req)
}

export const GET = (req: NextRequest) => {
    return handlers.GET(reqWithTrustedOrigin(req))
}

export const POST = (req: NextRequest) => {
    return handlers.POST(reqWithTrustedOrigin(req))
}
morochena commented 3 months ago

Having the same issue.

Interestingly, disabling prefetching in all my Link components throughout the app mitigates the issue, but it still happens relatively often.

Edit by maintainer bot: Comment was automatically minimized because it was considered unhelpful. (If you think this was by mistake, let us know). Please only comment if it adds context to the issue. If you want to express that you have the same problem, use the upvote 👍 on the issue description or subscribe to the issue for updates. Thanks!

mattjohnpowell commented 3 months ago

I'm having the same problem with basically the default setup. My db calls are pretty small (compared to other apps I have) so I'm just not sure of the problem. I've tried the above options. Running on the edge etc. Should I downgrade to v4?

    "@auth/prisma-adapter": "^2.4.2",
    "@neondatabase/serverless": "^0.9.4",
    "@prisma/adapter-neon": "^5.17.0",
    "@prisma/client": "^5.18.0",
    "prisma": "^5.18.0",
    "next": "14.2.5",
    "next-auth": "^5.0.0-beta.20",
`//middleware.ts

export { auth as middleware } from "@/auth"`

//auth.ts

import NextAuth from "next-auth"
import { PrismaAdapter } from "@auth/prisma-adapter"
import prisma from "@/lib/prisma"
import Google from "next-auth/providers/google"

export const { handlers, signIn, signOut, auth } = NextAuth({
  adapter: PrismaAdapter(prisma),
  providers: [Google],
})
// app\api\auth\[...nextauth]\route.ts
import { handlers } from "@/auth"

export const { GET, POST } = handlers
generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["driverAdapters"]
}

datasource db {
  provider  = "postgresql"
  url       = env("POSTGRES_PRISMA_URL")
  directUrl = env("POSTGRES_URL_NON_POOLING")
}

model User {
// /lib/prisma.ts

import { PrismaClient } from "@prisma/client";
import { PrismaNeon } from '@prisma/adapter-neon'
import { Pool } from '@neondatabase/serverless'

declare global {
  var prisma: PrismaClient | undefined;
}

const createPrismaClient = () => {
  const pool = new Pool({ connectionString: process.env.POSTGRES_PRISMA_URL })
  const adapter = new PrismaNeon(pool)
  return new PrismaClient({ adapter })
}

const prisma = global.prisma || createPrismaClient()

if (process.env.NODE_ENV === "development") global.prisma = prisma;

export default prisma;
mattjohnpowell commented 3 months ago

So i plugged the code and docs into Claude and after many iterations the below seems to work. Caveat: I don't know why, I can barely see any difference in what it does but for now it seems to work (MAJOR PROBLEM THOUGH: images do not seem to work, which isn't the end of the world for me but may be a deal breaker) if anyone can comment or help then that would be great

// middleware.ts
import { NextResponse } from 'next/server'
import { auth } from "./auth"

export default auth((req) => {
    // Exclude image and static asset requests from middleware processing
    if (
      req.nextUrl.pathname.startsWith('/_next') ||
      req.nextUrl.pathname.startsWith('/test') ||
      req.nextUrl.pathname.startsWith('/static') ||
      req.nextUrl.pathname.startsWith('/public') ||
      req.nextUrl.pathname.endsWith('.webp') ||
      req.nextUrl.pathname.endsWith('.png') ||
      req.nextUrl.pathname.endsWith('.jpg') ||
      req.nextUrl.pathname.endsWith('.jpeg') ||
      req.nextUrl.pathname.endsWith('.gif')
    ) {
      return NextResponse.next()
    }

    const isLoggedIn = !!req.auth
    const isAuthPage = req.nextUrl.pathname.startsWith('/api/auth')
    const isPublicPage = ['/', '/login'].includes(req.nextUrl.pathname) // Add other public pages here

    if (!isLoggedIn && !isAuthPage && !isPublicPage) {
      return NextResponse.redirect(new URL('/api/auth/signin', req.url))
    }

    return NextResponse.next()
  })

  export const config = {
    matcher: [
      /*
       * Match all request paths except for the ones starting with:
       * - api (API routes)
       * - _next/static (static files)
       * - _next/image (image optimization files)
       * - favicon.ico (favicon file)
       */
      '/((?!api|_next/static|_next/image|public|favicon.ico).*)',
    ],
  }
// auth.ts
import NextAuth from "next-auth"
import { authConfig } from "./auth.config"
import { PrismaAdapter } from "@auth/prisma-adapter"
import prisma from "@/lib/prisma"

export const { 
  handlers: { GET, POST },
  auth,
  signIn,
  signOut,
} = NextAuth({
  ...authConfig,
  adapter: PrismaAdapter(prisma),
  session: { strategy: "jwt" },
})
// auth.config.ts
import type { NextAuthConfig } from "next-auth"
import Google from "next-auth/providers/google"

export const authConfig = {
  providers: [Google],
  callbacks: {
    authorized({ auth, request: { nextUrl } }) {
      const isLoggedIn = !!auth?.user
      const isOnDashboard = nextUrl.pathname.startsWith('/dashboard')
      if (isOnDashboard) {
        if (isLoggedIn) return true
        return false // Redirect unauthenticated users to login page
      } else if (isLoggedIn) {
        return Response.redirect(new URL('/dashboard', nextUrl))
      }
      return true
    },
  },
} satisfies NextAuthConfig
// app\api\auth\[...nextauth]\route.ts
import { GET, POST } from "@/auth"

export { GET, POST }
michaelcummings12 commented 3 months ago

So i plugged the code and docs into Claude and after many iterations the below seems to work. Caveat: I don't know why, I can barely see any difference in what it does but for now it seems to work (MAJOR PROBLEM THOUGH: images do not seem to work, which isn't the end of the world for me but may be a deal breaker) if anyone can comment or help then that would be great

It looks your middleware is already not running on some of the routes that you've excluded from processing in your middleware.ts (/next, /static, /public). You could update your config to also not match on those image extensions.

mattjohnpowell commented 3 months ago

d'oh, didn't have the remote pattern in next.config

const nextConfig = { images: { remotePatterns: [ {

All works well now

ghdoergeloh commented 2 months ago

For me it was also the websocket as @mattgraphlan pointed out.

We added a db call in the session callback, so everytime the auth method in the middleware was called there was an additional db call. That caused our timeout.

The DB client we used looked like this:

import { Pool } from "@neondatabase/serverless";
import { drizzle } from "drizzle-orm/neon-serverless";
import { env } from "./env";
import * as schema from "./schema";

const pool = new Pool({ connectionString: env.DATABASE_URL });
const db = drizzle(pool, { schema });

export default db;

export { db };

We switched to the http client:

import { neon } from "@neondatabase/serverless";
import { drizzle } from "drizzle-orm/neon-http";
import { env } from "./env";
import * as schema from "./schema";

const sql = neon(env.DATABASE_URL);
const db = drizzle(sql, { schema });

export default db;

export { db };

then everything worked fine.

Since you cannot use transactions with the http client. And the usual pg driver has a better performance in node runtimes, we used another driver, if it runs in a node environment:

import { drizzle } from "drizzle-orm/node-postgres";
import pg from "pg";
import { env } from "./env";
import * as schema from "./schema";
const { Pool } = pg;

const pool = new Pool({ connectionString: env.DATABASE_URL });
const db = drizzle(pool, { schema });

export default db;

export { db };

The switch is simply done by the package.json of the db client through conditional exports:

{
  ...
  "exports": {
    "./client": {
      "types": "./dist/client.node.d.ts",
      "edge-light": "./src/client.edge.ts",
      "node": "./src/client.node.ts"
    },
    "./schema": {
      "types": "./dist/schema/index.d.ts",
      "default": "./src/schema/index.ts"
    }
  },
  ...
}
tobiasmeyhoefer commented 2 months ago

I got it fixed and talk about this and a couple other problems you might face in this Video:

https://youtu.be/aS25W7no8w0?si=g18rPvOn02eNFOwK

michaelcummings12 commented 2 months ago

I got it fixed and talk about this and a couple other problems you might face in this Video:

https://youtu.be/aS25W7no8w0?si=g18rPvOn02eNFOwK

I'm dying for a solution to this. Your method works, but it doesn't verify that the session is actually valid. Someone can easily spoof the cookie and trick your app into thinking they are signed in...

michaelcummings12 commented 2 months ago

After spending several weeks on this and trying a million different ways, I figured out a solution 😎. This uses JWT while still ensuring the session has the latest user data I need the user data to be current because the roles of a user can change and it needs to be reflected instantly.

Also, I am using neon for my database which provides a Prisma adapter that allows it to be used at the edge. In addition, this adapter supports WebSockets which further helps our performance.

In my authConfig.ts, I have (session set to jwt strategy)

adapter: PrismaAdapter(prisma),
session: {
    strategy: "jwt"
}

And for the session callback in authConfig.ts, I have the following

session: async ({ session, token }) => {
    const user = await prisma.user.findUniqueOrThrow({ where: { id: token.sub } });
    return { ...session, user };
}

Now for the most important part- I have an edgeAuth.ts file that I call from the middleware/edge. I import my base config from authConfig.ts and simply override the session callback to not fetch the user from the database.

const config = authConfig(prisma);
export const { auth } = NextAuth({
    ...config,
    callbacks: {
        ...config.callbacks,
        session: async ({ session }) => session
    }
});

prisma.ts

declare global {
    var prisma: PrismaClient | undefined;
}

neonConfig.webSocketConstructor = WebSocket;

const createPrismaClient = () => {
    const pool = new Pool({ connectionString: process.env.DATABASE_URL });
    const adapter = new PrismaNeon(pool);
    return new PrismaClient({ adapter })
};

const prisma = global.prisma || createPrismaClient();

if (process.env.NODE_ENV === "development") global.prisma = prisma;

export default prisma;

Finally, my middleware.ts as an example


import { auth } from "./utils/auth/edgeAuth";

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

export const middleware = auth((req) => {
    // Redirect to app if user is not an admin and tries to access private route.
    if (req.nextUrl.pathname.startsWith("/admin") && !req.auth) {
        return Response.redirect(new URL("/", req.nextUrl.origin));
    }
});
OGPowell commented 2 months ago

Having the same issue with the Prisma Neon Adapter. Curious if there is a fix to this.

lohrm-stabl commented 2 weeks ago

Same issues with Drizzle Neon Serverless adapter

import { Pool } from "@neondatabase/serverless";
import { drizzle } from "drizzle-orm/neon-serverless";

const pool = new Pool({
    connectionString: serverConfig.databaseConnectionString,
});

export const db = drizzle(pool, {});
noqcks commented 1 week ago

The problem is that authjs + db adapter + edge dont work well together.

The documentation goes into detail about this here: https://authjs.dev/guides/edge-compatibility

Look at Split Config for a solution.

Here's how I did it

auth.config.js (NOTE: no db adapter hooked up)

import type { NextAuthConfig } from "next-auth";
import { type Provider } from "next-auth/providers";
import GoogleProvider from "next-auth/providers/google";
import { env } from "../env.mjs";

export const providers: Provider[] = [
  GoogleProvider({
    clientId: env.AUTH_GOOGLE_CLIENT_ID,
    clientSecret: env.AUTH_GOOGLE_CLIENT_SECRET,
    // since we trust google has securely verified the email for all users
    // we can automatically link the user to an account in the db
    // https://next-auth.js.org/configuration/providers/oauth#allowdangerousemailaccountlinking-option
    allowDangerousEmailAccountLinking: true,
    profile(_profile) {
      return {
        id: _profile.sub,
        firstName: _profile.given_name,
        lastName: _profile.family_name,
        email: _profile.email,
        imageUrl: _profile.picture,
        role: null,
      };
    },
    authorization: {
      params: {
        prompt: "consent",
        access_type: "offline",
        response_type: "code",
      },
    },
  }),
];

// this is a next auth config object that only has the providers
// and DOES NOT have a db adapater. This is so that we can use this
// at the edge (such as in middleware)
// read more here: https://authjs.dev/guides/edge-compatibility
export default {
  theme: {
    logo: "/logo.png",
  },
  providers,
  session: {
    // jwt is required for us, because our middleware runs at the edge
    // and we cant make prisma calls there
    strategy: "jwt",
  },
  callbacks: {
    jwt({ token, user }) {
      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
      if (user?.id) {
        // User is available during sign-in
        token.firstName = user.firstName;
        token.lastName = user.lastName;
        token.role = user.role;
        token.id = user.id;
      }

      return token;
    },
    session({ token, session }) {
      return {
        ...session,
        user: {
          id: token.id,
          role: token.role,
          firstName: token.firstName,
          lastName: token.lastName,
          imageUrl: token.imageUrl,
        },
      };
    },
  },
} satisfies NextAuthConfig;

then my middleware.ts

import authConfig from "~/auth/auth.config";

const { auth } = NextAuth(authConfig);

export default auth((req) => {  
   ...
}

and finally my auth object with db adapter in auth.ts

import NextAuth from "next-auth";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { prisma } from "@/db";
import authConfig from "./auth.config";

export const { auth, handlers, signIn, signOut } = NextAuth({
  adapter: PrismaAdapter(prisma) as Adapter,
  ...authConfig,
} satisfies NextAuthConfig);

and nextauth.d.ts

import { type User as DatabaseUser } from "@/db/prisma/zod/modelSchema/UserSchema";
import { type AgentProfile } from "@/db/prisma/zod/modelSchema/AgentProfileSchema";

import "next-auth/jwt";

type SessionUser = DatabaseUser> & {
  brokerageId: string | null;
};

declare module "next-auth/adapters" {
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
  interface AdapterUser extends SessionUser {}
}

declare module "next-auth/jwt" {
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
  interface JWT extends SessionUser {}
}

declare module "next-auth" {
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
  interface User extends SessionUser {}

  interface Session {
    user: SessionUser;
  }
}