Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

getServerSession does not work in Next 13.4 Server Functions when they are imported in client components #7486

Closed
Escapado opened this issue May 9, 2023 · 14 comments
Labels
triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime.

Comments

@Escapado
Copy link

Escapado commented May 9, 2023

Environment

System:
    OS: macOS 13.3.1
    CPU: (12) arm64 Apple M2 Max
    Memory: 18.51 GB / 64.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 16.19.0 - ~/.nvm/versions/node/v16.19.0/bin/node
    npm: 8.19.3 - ~/.nvm/versions/node/v16.19.0/bin/npm
  Browsers:
    Brave Browser: 113.1.51.110
    Chrome: 113.0.5672.92
    Edge: 113.0.1774.35
    Safari: 16.4

Reproduction URL

https://codesandbox.io/p/sandbox/awesome-johnson-o9i04r

Describe the issue

When using server actions in client components in the new Next 13.4 update, getServerSession fails with Error: Invariant: Method expects to have requestAsyncStorage, none available.

Before the Next 13.4.2-canary.2 release the cookies and headers function from next/headers failed so I thought this was the reason. However with the new release this should be fixed (see here, and try out in the reproduction repo).
Calling cookies and headers now works. Interestingly enough, doing the same fails in next auth. The stack trace reveals that
the following lines in the next-auth package are responsible

    const {
      headers,
      cookies
    } = require("next/headers");

    req = {
      headers: Object.fromEntries(headers()), // <- This fails. Is it due to the way next/headers is imported using require?
      cookies: Object.fromEntries(cookies().getAll().map(c => [c.name, c.value]))
};

Please note:
Both server actions work when called in server components. Only when importing in a client component the first one breaks.

How to reproduce

  1. Go to the reproduction repo.
  2. Look at the two server actions in action.ts and how they are invoked in page.tsx
  3. Click on the Invoke Server Action! Button and observe console output. (Works)
  4. Click on the Invoke Next Auth Server Action! Button and observe console output. (Breaks)

Expected behavior

The expected behaviour would be for getServerSession to work in server actions which are imported in client components.

@Escapado Escapado added the triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime. label May 9, 2023
@Escapado Escapado changed the title getServerSession does not work in Next 13.4 Server Functions getServerSession does not work in Next 13.4 Server Functions when they are imported in client components May 9, 2023
@balazsorban44
Copy link
Member

image

Your reproduction worked in both cases, so I assume this was fixed upstream then.

@Escapado
Copy link
Author

Escapado commented May 9, 2023

image

Your reproduction worked in both cases, so I assume this was fixed upstream then.

Screenshot 2023-05-09 at 14 47 24

@balazsorban44 Sorry about the confusion! It will return but the server will throw an error (see screenshot) and the session is not there.

@timootten
Copy link

timootten commented May 9, 2023

temporary fix (for me):

const req = {
headers: Object.fromEntries(headers() as Headers),
cookies: Object.fromEntries(
cookies()
.getAll()
.map((c) => [c.name, c.value])
),
}
const session = await getSession({ req });
return session;

@Rykuno
Copy link

Rykuno commented May 9, 2023

@balazsorban44 this issue still persists even w/ the 13.4.2-canary.2 release of Next.

@ludwigbacklund
Copy link

ludwigbacklund commented May 11, 2023

Here is a workaround that you can use if you want to call getServerSession in a server action from a client component until the bug gets fixed:

import { cookies, headers } from 'next/headers';
import { getServerSession as originalGetServerSession } from 'next-auth';

export const getServerSession = async () => {
  const req = {
    headers: Object.fromEntries(headers() as Headers),
    cookies: Object.fromEntries(
      cookies()
        .getAll()
        .map((c) => [c.name, c.value]),
    ),
  };
  const res = { getHeader() {}, setCookie() {}, setHeader() {} };
  const authOptions = { <insert auth options here> }

  // @ts-ignore - The type used in next-auth for the req object doesn't match, but it still works 
  const session = await originalGetServerSession(req, res, authOptions);
  return session;
};

@Rykuno
Copy link

Rykuno commented May 12, 2023

This absolutely works. Have you created PR for this yet @ludwigbacklund ?

@ludwigbacklund
Copy link

This absolutely works. Have you created PR for this yet @ludwigbacklund ?

I have not. In fact, I'm not sure why it even works as I basically just copied the original implementation from next-auth. Like someone mentioned, it might have to do with require vs import for the Next.js headers/cookies functions but I'm not sure.

@benjaminwaterlot
Copy link

The problem persists with next-auth@4.22.1 and next@13.4.3-canary.0, thanks for the workaround @ludwigbacklund. 🙏

@beltran120394
Copy link

Thanks @ludwigbacklund 🙌
Thanks for the help, it worked correctly in my application, is there a way to get this to next-auth soon?

@abencun-symphony
Copy link

The workaround posted by @ludwigbacklund works - but is this going to be fixed?

@mcnaveen
Copy link

mcnaveen commented Jun 7, 2023

I'm not using the app directory. I'm on the pages directory performing SSR with getServerSideProps. The Session strategy is set to a database.

Here is the Issue I created: #7752

@nphivu414
Copy link

Here is a workaround that you can use if you want to call getServerSession in a server action from a client component until the bug gets fixed:

import { cookies, headers } from 'next/headers';
import { getServerSession as originalGetServerSession } from 'next-auth';

export const getServerSession = async () => {
  const req = {
    headers: Object.fromEntries(headers() as Headers),
    cookies: Object.fromEntries(
      cookies()
        .getAll()
        .map((c) => [c.name, c.value]),
    ),
  };
  const res = { getHeader() {}, setCookie() {}, setHeader() {} };
  const authOptions = { <insert auth options here> }

  // @ts-ignore - The type used in next-auth for the req object doesn't match, but it still works 
  const session = await originalGetServerSession(req, res, authOptions);
  return session;
};

thanks for the workaround @ludwigbacklund, much appreciated

@ApollonNishikawa
Copy link

Here is a workaround that you can use if you want to call getServerSession in a server action from a client component until the bug gets fixed:

import { cookies, headers } from 'next/headers';
import { getServerSession as originalGetServerSession } from 'next-auth';

export const getServerSession = async () => {
  const req = {
    headers: Object.fromEntries(headers() as Headers),
    cookies: Object.fromEntries(
      cookies()
        .getAll()
        .map((c) => [c.name, c.value]),
    ),
  };
  const res = { getHeader() {}, setCookie() {}, setHeader() {} };
  const authOptions = { <insert auth options here> }

  // @ts-ignore - The type used in next-auth for the req object doesn't match, but it still works 
  const session = await originalGetServerSession(req, res, authOptions);
  return session;
};

Thank you so much @ludwigbacklund
It works for me when I use route.ts.
But I implemented it in a slightly different way.

This is my use case for getting login user from session info. We can get required data from NextReuqest.
When I want to get login user, I can call getCurrentUserAtServer inside some route.ts file.

import { NextRequest } from 'next/server';
import { getServerSession } from 'next-auth';

import { nextAuthOptions } from '@/app/api/auth/[...nextauth]/route';
import { prismaClient } from '@/utils/prismaClient';

const getReq = (request: NextRequest) => ({
	headers: Object.fromEntries(request.headers),
	cookies: Object.fromEntries(
		request.cookies.getAll().map((c) => [c.name, c.value]),
	),
});
const res = { getHeader() {}, setCookie() {}, setHeader() {} };

export const getCurrentUserAtServer = async (request: NextRequest) => {
	// @ts-ignore
	const session = await getServerSession(getReq(request), res, nextAuthOptions);
	const email = session?.user?.email;
	const role = session?.user?.role;

	if (!email || role !== 'user') return null;

	const currentUser = await prismaClient.user.findFirst({
		where: { email },
	});

	return currentUser;
};

@Steravy
Copy link

Steravy commented Dec 31, 2023

In my case the getServerSection was returning always null.
I have Tried all the suggestion above but it didn't work for me. But when i defined the env variables:
NEXTAUTH_SECRET=asecretyoucandefine NEXTAUTH_URL=http://localhost:3000
it worked.
I think that the fact that the token and section structure has been changed would cause a failure in the decoding and encoding of the token using the default parameters for this purpose, which would result in the error of the getServerSession always returning null. Because i did changed their structure to fit my requirements. By providing those env variables the problem was gone. But this is just a guest, and i am saying this because i was having some logs pointig for error in decoding:

stack: 'JWEDecryptionFailed: decryption operation failed\n' + ' at gcmDecrypt (webpack-internal:///(sc_server)/../../../node_modules/jose/dist/node/esm/runtime/decrypt.js:81:15)\n' + ' at decrypt (webpack-internal:///(sc_server)/../../../node_modules/jose/dist/node/esm/runtime/decrypt.js:104:20)\n' + ' at flattenedDecrypt (webpack-internal:///(sc_server)/../../../node_modules/jose/dist/node/esm/jwe/flattened/decrypt.js:157:90)\n' + ' at async compactDecrypt (webpack-internal:///(sc_server)/../../../node_modules/jose/dist/node/esm/jwe/compact/decrypt.js:56:23)\n' + ' at async jwtDecrypt (webpack-internal:///(sc_server)/../../../node_modules/jose/dist/node/esm/jwt/decrypt.js:46:23)\n' + ' at async Object.decode (webpack-internal:///(sc_server)/../../../node_modules/next-auth/jwt/index.js:44:26)\n' + ' at async Object.session (webpack-internal:///(sc_server)/../../../node_modules/next-auth/core/routes/session.js:59:34)\n' + ' at async AuthHandler (webpack-internal:///(sc_server)/../../../node_modules/next-auth/core/index.js:221:37)\n' + ' at async getServerSession (webpack-internal:///(sc_server)/../../../node_modules/next-auth/next/index.js:123:21)\n' + ' at async SessionProvider (webpack-internal:///(sc_server)/./app/layout.tsx:22:21)', name: 'JWEDecryptionFailed'

I don`t know if this are the same issue as yours but just in case, i am posting it here. I will do some research to better understand the subject and i will come back to this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime.
Projects
None yet
Development

No branches or pull requests