next-auth: getServerSession is null in Next.js API routes (within the app directory)

Environment

System:
  OS: macOS 13.3.1
  CPU: (8) arm64 Apple M2
  Memory: 95.55 MB / 8.00 GB
  Shell: 5.9 - /bin/zsh
Binaries:
  Node: 19.2.0 - ~/.nvm/versions/node/v19.2.0/bin/node
  Yarn: 1.22.19 - ~/.nvm/versions/node/v19.2.0/bin/yarn
  npm: 9.6.2 - ~/.nvm/versions/node/v19.2.0/bin/npm
Browsers:
  Chrome: 112.0.5615.137
  Safari: 16.4

Reproduction URL

https://github.com/ghoshnirmalya/the-fullstack-app

Describe the issue

The following code always returns null if I do console.log(session) when the code is present inside the Next.js API Routes within the app directory:

export async function GET() {
  try {
    const session = await getServerSession(authOptions);
    ..
}

You can find the relevant code here.

However, the following code from the React Server Components returns the correct data:

export default async function ProjectIndexPage() {
  const session = await getServerSession(authOptions);
  ..
  
  return (..)
}

The session in the above case is something like the following:

{
  user: {
    name: 'John Doe',
    email: 'john@doe.com',
    image: 'https://lh3.googleusercontent.com/john-doe',
    id: '12345678910'
  }
}

You can find the relevant code here.

How to reproduce

  1. Clone the repository:
    git clone git@github.com:ghoshnirmalya/the-fullstack-app.git
    
  2. Install the necessary dependencies:
    pnpm install
    
  3. Add the necessary env vars:
    DATABASE_URL='mysql://database-url'
    NEXT_PUBLIC_VERCEL_URL=127.0.0.1:3000
    NEXTAUTH_SECRET=some-secret
    NEXTAUTH_URL=http://127.0.0.1:3000
    GOOGLE_CLIENT_ID=google-client-id
    GOOGLE_CLIENT_SECRET=google-client-secret
    
  4. Generate the Prisma client:
    npx prisma db push && npx prisma generate
    
  5. Run the development server:
    pnpm run dev
    

Expected behavior

The session should return the correct object from the API Routes. The console.log(session) should return something like the following:

{
  user: {
    name: 'John Doe',
    email: 'john@doe.com',
    image: 'https://lh3.googleusercontent.com/john-doe',
    id: '12345678910'
  }
}

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 12
  • Comments: 18 (4 by maintainers)

Most upvoted comments

This is expected, based on your reproduction.

The issue in your case (and likely the same for your @freshtechs) is that you are invoking a Route Handler via fetch from a Server Component. By default, cookies are not passed in a server-side fetch call for security reasons (to avoid accidentally attaching cookies to third-party API calls). You have 2 options:

  1. Don’t use a Route Handler: Since you are already in a server context and just reading some data, you can import the function that returns the data directly.
  2. Make sure to pass the headers to the fetch call:
import { headers } from "next/headers"

// ...
const response = await fetch(getApiUrl("api/projects"), {
  method: "GET",
  headers: headers()
})

In your case, option 1 makes more sense.

This is expected, based on your reproduction. The issue in your case (and likely the same for your @freshtechs) is that you are invoking a Route Handler via fetch from a Server Component. By default, cookies are not passed in a server-side fetch call for security reasons (to avoid accidentally attaching cookies to third-party API calls). You have 2 options:

1. Don't use a Route Handler: Since you are already in a server context and just reading some data, you can import the function that returns the data directly.

2. Make sure to pass the headers to the fetch call:
import { headers } from "next/headers"

// ...
const response = await fetch(getApiUrl("api/projects"), {
  method: "GET",
  headers: headers()
})

In your case, option 1 makes more sense.

It worked in dev mode, but not in production. Vercel is showing me this error log:

[Error]: Headers cannot be modified. Read more: https://nextjs.org/docs/app/api-reference/functions/headers

  • Change headers() -> new Headers(headers())

@balazsorban44 Thanks so much for the reply.

I can confirm that I tried the option 2 and it works fine. However, I’m getting the following TypeScript error:

Type 'ReadonlyHeaders' is not assignable to type 'HeadersInit | undefined'.
  Type 'ReadonlyHeaders' is missing the following properties from type 'Headers': append, delete, set
Screenshot 2023-05-02 at 2 41 15 PM

I’m guessing that this error arises because the Next.js headers are only read-only for now. Any way in which I can resolve this other than ignoring it?

Regarding option 1, do you recommend that I use Prisma directly in the server components like the following instead of the API Route?

export default async function ProjectIndexPage() {
-  const response = await fetch(getApiUrl("api/projects"), {
-    method: "GET",
-    cache: "no-store",
-    headers: headers(),
-  });
-  const projects: Project[] = await response.json();

+  const projects = await prisma.project.findMany({
+    where: {
+      creatorId: "1",
+    },
+  });

  return (..)
}

Im also seeing this behaviour when fetching (fetch(http://localhost:3000/api/hello)) from a React Server Component to any api route.ts. But if full url is manually entered in browser, the getServerSession returns a correct value. I deleted my middleware.ts trying to debug without success.

This is expected, based on your reproduction.

The issue in your case (and likely the same for your @freshtechs) is that you are invoking a Route Handler via fetch from a Server Component. By default, cookies are not passed in a server-side fetch call for security reasons (to avoid accidentally attaching cookies to third-party API calls). You have 2 options:

1. Don't use a Route Handler: Since you are already in a server context and just reading some data, you can import the function that returns the data directly.

2. Make sure to pass the headers to the fetch call:
import { headers } from "next/headers"

// ...
const response = await fetch(getApiUrl("api/projects"), {
  method: "GET",
  headers: headers()
})

In your case, option 1 makes more sense.

It worked in dev mode, but not in production. Vercel is showing me this error log:

[Error]: Headers cannot be modified. Read more: https://nextjs.org/docs/app/api-reference/functions/headers

Thinking ahead of you. 😉 https://github.com/vercel/next.js/pull/49075

My recommendation would be to just extract the Route Handler into a function like getProjects(session) or similar for convenience. Note, you can have that in a separate file if you need to, and have it next to the page. (Colocation is one of the benefits of using App Router architecture IMO).

If you worry about performance, check out https://beta.nextjs.org/docs/data-fetching/fetching#data-fetching-without-fetch and https://beta.nextjs.org/docs/data-fetching/caching#per-request-caching

I worked around this by getting jwt token instead in RSC,

const jwt = await getToken({
    // `getToken` only need these attributes, to make it compatible with app router
    // This code is copied from `getServerSession`
    req: {
      headers: Object.fromEntries(headers() as Headers),
      cookies: Object.fromEntries(
        cookies()
          .getAll()
          .map((c) => [c.name, c.value])
      )
    } as unknown as NextRequest
  });