nextauthjs / next-auth

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

CLIENT_FETCH_ERROR - Unexpected token E in JSON at position 0 #7166

Closed nfts2me closed 1 year ago

nfts2me commented 1 year ago

Environment

System: OS: macOS 13.3 CPU: (8) arm64 Apple M2 Memory: 1.25 GB / 16.00 GB Shell: 5.9 - /bin/zsh Binaries: Node: 16.17.0 - ~/.nvm/versions/node/v16.17.0/bin/node Yarn: 1.22.19 - ~/.nvm/versions/node/v16.17.0/bin/yarn npm: 9.6.3 - ~/development/nfts2me/nfts2me/node_modules/.bin/npm Browsers: Brave Browser: 105.1.43.93 Chrome: 112.0.5615.49 Firefox: 110.0.1 Safari: 16.4

Reproduction URL

not-available

Describe the issue

After upgrading from next-auth@4.20.1 to next-auth@4.21.1 we're facing this error:


[next-auth][error][CLIENT_FETCH_ERROR] 
https://next-auth.js.org/errors#client_fetch_error invalid json response body at http://localhost:3000/api/auth/csrf/ reason: Unexpected token E in JSON at position 0 {
  error: {
    message: 'invalid json response body at http://localhost:3000/api/auth/csrf/ reason: Unexpected token E in JSON at position 0',
    stack: 'FetchError: invalid json response body at http://localhost:3000/api/auth/csrf/ reason: Unexpected token E in JSON at position 0\n' +
      '    at /nfts2me/node_modules/next/dist/compiled/node-fetch/index.js:1:51220\n' +
      '    at runMicrotasks (<anonymous>)\n' +
      '    at processTicksAndRejections (node:internal/process/task_queues:96:5)',
    name: 'FetchError'
  },
  url: 'http://localhost:3000/api/auth/csrf',
  message: 'invalid json response body at http://localhost:3000/api/auth/csrf/ reason: Unexpected token E in JSON at position 0'
}```

When trying to authenticate using Web3 provider (https://github.com/spruceid/siwe-next-auth-example).

The error happens on the "authorize" function on this line:
```          if (siwe.nonce !== (await getCsrfToken({ req }))) {```
When calling "getCsrfToken".

When rolling back to next-auth@4.20.1 it works with no problems.

### How to reproduce

Try to sign with Web3.

### Expected behavior

Authenticate.
hkrawczyk commented 1 year ago

have exactly the same issue.

Seems like getCsrfToken in the authorize method is throwing error

github-actions[bot] commented 1 year ago

We cannot recreate the issue with the provided information. Please add a reproduction in order for us to be able to investigate.

Why was this issue marked with the incomplete label?

To be able to investigate, we need access to a reproduction to identify what triggered the issue. We prefer a link to a public GitHub repository (template), but you can also use a tool like CodeSandbox or StackBlitz.

To make sure the issue is resolved as quickly as possible, please make sure that the reproduction is as minimal as possible. This means that you should remove unnecessary code, files, and dependencies that do not contribute to the issue.

Please test your reproduction against the latest version of NextAuth.js (next-auth@latest) to make sure your issue has not already been fixed.

I added a link, why was it still marked?

Ensure the link is pointing to a codebase that is accessible (e.g. not a private repository). "example.com", "n/a", "will add later", etc. are not acceptable links -- we need to see a public codebase. See the above section for accepted links.

What happens if I don't provide a sufficient minimal reproduction?

Issues with the incomplete label that receives no meaningful activity (e.g. new comments with a reproduction link) are closed after 7 days.

If your issue has not been resolved in that time and it has been closed/locked, please open a new issue with the required reproduction. (It's less likely that we check back on already closed issues.)

I did not open this issue, but it is relevant to me, what can I do to help?

Anyone experiencing the same issue is welcome to provide a minimal reproduction following the above steps. Furthermore, you can upvote the issue using the :+1: reaction on the topmost comment (please do not comment "I have the same issue" without repro steps). Then, we can sort issues by votes to prioritize.

I think my reproduction is good enough, why aren't you looking into it quicker?

We look into every NextAuth.js issue and constantly monitor open issues for new comments.

However, sometimes we might miss one or two. We apologize, and kindly ask you to refrain from tagging core maintainers, as that will usually not result in increased priority.

Upvoting issues to show your interest will help us prioritize and address them as quickly as possible. That said, every issue is important to us, and if an issue gets closed by accident, we encourage you to open a new one linking to the old issue and we will look into it.

Useful Resources

codingwithmanny commented 1 year ago

Getting the same issue after installing v4.21.1 reverted back to 4.20.1 to ensure things are working.

codingwithmanny commented 1 year ago

How To Reproduce

1 - Scaffold new project - pnpm create t3-app@latest; 2 - Modify ./src/server/auth.ts under the getServerAuthSession as follows:

File: ./src/server/auth.ts

// ...

// NOTE: This is needed for passing the `req` to the authOptions to verify the csrf with the nonce for Sign-In With Ethereum

/**
 * Wrapper for `getServerSession` so that you don't need to import the `authOptions` in every file.
 *
 * @see https://next-auth.js.org/configuration/nextjs
 */
export const getServerAuthSession = async (ctx: {
  req: GetServerSidePropsContext["req"];
  res: GetServerSidePropsContext["res"];
}) => {
  // Add next two lines to retrieve csrf token
  const token = await getCsrfToken({ req: ctx.req });
  console.log({ token });
  return getServerSession(ctx.req, ctx.res, authOptions);
};

3 - Run the dev environment and see error in terminal - pnpm dev

Expected Behavior

With version next-auth@4.20.1

pnpm dev;

# Output:
# {
#   token: '2f9e2ad7596966b47e4d631ad3cbe48d246781b91f8680a840906a02529aacb3'
# }

Current Behavior

With version next-auth@4.21.1

pnpm dev;

# Output:
# [next-auth][error][CLIENT_FETCH_ERROR] 
# https://next-auth.js.org/errors#client_fetch_error invalid json response body at http://localhost:3000/api/auth/csrf reason: # Unexpected token E in JSON at position 0 {
#   error: {
#     message: 'invalid json response body at http://localhost:3000/api/auth/csrf reason: Unexpected token E in JSON at # position 0',
#     stack: 'FetchError: invalid json response body at http://localhost:3000/api/auth/csrf reason: Unexpected token E in JSON # at position 0\n' +
#       '    at /Users/username/path/to/test-t3-app/node_modules/.pnpm/next@13.2.4_react-# dom@18.2.0_react@18.2.0/node_modules/next/dist/compiled/node-fetch/index.js:1:51220\n' +
#       '    at runMicrotasks (<anonymous>)\n' +
#       '    at processTicksAndRejections (node:internal/process/task_queues:96:5)',
#     name: 'FetchError'
#   },
#   url: 'http://localhost:3000/api/auth/csrf',
#   message: 'invalid json response body at http://localhost:3000/api/auth/csrf reason: Unexpected token E in JSON at # position 0'
# }
# { token: undefined }
codingwithmanny commented 1 year ago

@balazsorban44 does the above complete the section required for the steps to reproduce to remove the incomplete tag?

prometixX commented 1 year ago

I get the same Error when I invoke await getCsrfToken({ req }) inside the authorize method of a credentials provider. I think I found the issue or at least some more details why this happens.

The original error when decoding the response, in fetchData which is invoked by getCsrfToken, to text instead of json, it says: Error: This action with HTTP POST is not supported by NextAuth.js

In fetchData there is a check if the request has a body

if (req?.body) {
      options.body = JSON.stringify(req.body)
      options.method = "POST"
    }

if so, fetching the Csrf-Token is done with a POST request which doesnt seem to work. You can check when running a POST request to http://localhost:3000/api/auth/csrf it responses with Error: This action with HTTP POST is not supported by NextAuth.js

Maybe the fetchData method has changed and introduced this bug?

balazsorban44 commented 1 year ago

@codingwithmanny I would like a repro that I can easily clone and can start the server to reproduce the issue. I'm not familiar with the internals/code of t3-app in detail, so it would be nice if you could create a reproduction without it, just the bare minimal issue. Thanks. Not sure how/when getServerAuthSession is being used.

@prometixX It's unclear what you are trying to achieve. What you are describing seems to be unnecessary. When the authorize callback is invoked, the request has already been verified against CSRF. If you are calling an external endpoint, our CSRF protection should not be used for it. If you are invoking another endpoint in your app, just import the logic rather than doing a subsequent fetch call.

codingwithmanny commented 1 year ago

Absolutely @balazsorban44 Here is a repository: https://github.com/codingwithmanny/t3-app-siwe

The only thing that needs to be done is update the version pnpm add next-auth@latest. The README should show how to set it up.

For some additional context, this is an implementation with Sign-In With Ethereum (SIWE) and Create-t3-stack, and it'll require a Crypto wallet like https://metamask.io.

The way that we're leverage getCsrfToken is to leverage and use as a nonce needed for the SIWE wallet message prompt.

prometixX commented 1 year ago

@balazsorban44 I basically had the same usecase that @codingwithmanny is describing. Using the getCsrfToken in the authorize method of a credentials provider to verify the SIWE message. Using getCsrfToken is how they are doing it in the Docs here https://docs.login.xyz/integrations/nextauth.js.

async authorize(credentials) {
        try {
          const siwe = new SiweMessage(JSON.parse(credentials?.message || "{}"))
          const nextAuthUrl = new URL(process.env.NEXTAUTH_URL)

          const result = await siwe.verify({
            signature: credentials?.signature || "",
            domain: nextAuthUrl.host,
            nonce: await getCsrfToken({ req }),
          })

          if (result.success) {
            return {
              id: siwe.address,
            }
          }
          return null
        } catch (e) {
          return null
        }
      },

But due to some change within NextAuth (maybe inside fetchData) this doesnt seem to work anymore.

I understand that it might be an unnecessary overhead to call a fetch inside the api route (authorize method), since we have access to the cookie and NextAuth environment variables anyways but I didnt want to go the whole way of reimplementing the validation logic, so I thought using getCsrfToken (and with that a extra fetch) is ok for my usecase.

I think a lot of people will have this error since basically every SIWE + NextAuth tutorial uses the getCsrfToken method.

codingwithmanny commented 1 year ago

@prometixX I was thinking about this as well, where you could have a separate endpoint for the nonce and still verify the source request via csrf. I wonder if that's the new direction we should be taking with SIWE.

balazsorban44 commented 1 year ago

So after thinking about it/having a look, this code block is the issue:

https://github.com/codingwithmanny/t3-app-siwe/blob/591fa139fb34cc63e377433794f9012407a13cf4/src/server/auth.ts#L148-L151

getCSRFToken is not very ideal on the backend, since it will invoke another endpoint and if you are using something like Vercel/Serverless, it might be the cause of slower response times.

Longer term, I would recommend finding an alternative solution, or as a workaround, instead of passing the whole req (which is a POST request with a body when in the authorize() call's context), you can just pass an object with the cookies, since that's the only things we need:

https://github.com/nextauthjs/next-auth/blob/7a8c0068c4c42761b54eff496f6cb8820b54b5d1/packages/next-auth/src/client/_utils.ts#L43

So this should solve the issue:

- const nonce = await getCsrfToken({ req });
+ const nonce = await getCsrfToken({ req: { headers: req.headers } });

This will make sure that the following code block is not triggered, making the response invalid: https://github.com/nextauthjs/next-auth/blob/7a8c0068c4c42761b54eff496f6cb8820b54b5d1/packages/next-auth/src/client/_utils.ts#L47-L50

I would consider this kind of an edge case/not something we have official built-in support for, so I'm inclined to close the issue as it's not a bug, but I hope my above suggestion will be sufficient.

nfts2me commented 1 year ago
- const nonce = await getCsrfToken({ req });
+ const nonce = await getCsrfToken({ req: { headers: req.headers } });

I was re-checking the docs, and I still think that NextAuth introduced a bug, or the docs are wrong: Check this: https://next-auth.js.org/getting-started/client#server-side-example It explicitly gives the example for the server side to be done as the 'wrong?' code.

I would be nice to change either.

shunkakinoki commented 1 year ago

Resolved w/ downgrade to next-auth@4.20.1

shunkakinoki commented 1 year ago

The issue still persists w/ on next-auth with app dir

const nonce = await getCsrfToken({ req: { headers: req.headers } });

Resolved w/ downgrade as the comment above

dzkfzn commented 1 year ago

Getting the same issue after installing v4.21.1 reverted back to 4.20.1 to ensure things are working.

thanks

lveillard commented 1 year ago

Suddenly starting having the same issue with getSession. Without updating, I was on 4.19.0

Updating to 4.20.1 fixes it Updating to 4.21.1 or latest (4.24.4) makes the error appear again

Similar to what happens with getCsrfToken, this also workarounds the issue:

const session = await getSession({ req: { headers: req.headers } }); but generates TS errors

GAkim commented 1 year ago

Good morning I was managed to resolve an issue by using const session = await getServerAuthSession(context); instead of const session = await getSession(context);

Zimtente commented 11 months ago

Good morning I was managed to resolve an issue by using const session = await getServerAuthSession(context); instead of const session = await getSession(context);

im using server session but still getting the issue.

babamovandrej commented 8 months ago

I was facing the same issue in production when I was trying to deploy to Vercel. What worked for me was turning off the Vercel Authentication in Project Settings > Deployment Protection. Hope this helps!