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

500 Received an instance of URLSearchParams

Open alimozdemir opened this issue 1 year ago • 15 comments

Environment

Reproduction

It is hard to reproduce this error without sharing the secrets. But I did a debugging with loading nuxt-auth package directly from the source. I will describe all findings.

Describe the bug

First of all I would like to share my handler file.

import { decode } from 'jsonwebtoken'
import AzureAdProvider from 'next-auth/providers/azure-ad'
import { NuxtAuthHandler } from '#auth'

async function callRefreshToken(accessToken: any) {
  const url = `https://login.microsoftonline.com/${process.env.AZURE_AD_TENANT_ID}/oauth2/v2.0/token`;
  const req = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body:
      `grant_type=refresh_token` +
      `&client_secret=${process.env.AZURE_AD_CLIENT_SECRET}` +
      `&refresh_token=${accessToken.refreshToken}` +
      `&client_id=${process.env.AZURE_AD_CLIENT_ID}`,
  });
  const res = await req.json();
  return res;
}

async function refreshAccessToken(accessToken: any) {
  try {
    console.log('Previous token expires at', new Date(accessToken.accessTokenExpires));
    console.log('Refreshing access token...');
    const msToken = await callRefreshToken(accessToken);
    console.log('New token received');
    setAccessToken(accessToken, msToken);
    console.log('Access token refreshed');
    console.log('Next token expires at', new Date(accessToken.accessTokenExpires));
    return accessToken;
  } catch (error) {
    console.error(error);
    return {
      ...accessToken,
      error: 'RefreshAccessTokenError',
    };
  }
}

// Persist the access_token in the encrypted JWT.
function setAccessToken(jwt: any, msToken: any) {
  if (!msToken) {
    return;
  }

  if (msToken.access_token) {
    const decoded = decode(msToken.access_token)
    jwt.accessToken = msToken.access_token;
    if (decoded && typeof decoded !== 'string')
      jwt.roles = decoded.roles;
  }

  if (msToken.expires_at)
    jwt.accessTokenExpires = msToken.expires_at * 1000;
  else if (msToken.expires_in)
    jwt.accessTokenExpires = Date.now() + msToken.expires_in * 1000;

  jwt.refreshToken = msToken.refresh_token;
}

export default NuxtAuthHandler({
  // A secret string you define, to ensure correct encryption
  secret: process.env.AUTH_APP_SECRET,
  pages: {
    signIn: '/auth/signIn',
    signOut: '/auth/signOut',
    error: '/auth/error',
    verifyRequest: '/auth/verifyRequest',
    newUser: '/auth/new-user'
  },
  callbacks: {
    async jwt({ token, account, profile }) {
      setAccessToken(token, account);

      if (token.accessTokenExpires && Date.now() < token.accessTokenExpires) {
        return token;
      }

      return refreshAccessToken(token);
    },
    async session({ session, token }) {
      // Make access token available on the client.
      session.roles = token.roles;

      return session;
    },
  },
  providers: [
    // @ts-expect-error You need to use .default here for it to work during SSR. May be fixed via Vite at some point
    AzureAdProvider.default({
      clientId: process.env.AZURE_AD_CLIENT_ID,
      clientSecret: process.env.AZURE_AD_CLIENT_SECRET,
      tenantId: process.env.AZURE_AD_TENANT_ID,
      authorization: {
        params: {
          scope: `openid offline_access profile api://Boss-Dev/Admin`,
        },
      },
    })

  ]
})

Whenever I login or logout I receive following exception

[500] The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received an instance of URLSearchParams

error on https://github.com/sidebase/nuxt-auth/blob/c5a62d99784a64cd208a0845da0a37461337f7cc/src/runtime/server/services/authjs/nuxtAuthHandler.ts#L206

somehow, h3 package readBody throws this error, but I already test that readBody function with URLSearchParams object in a clean project and it is working ok.

The only thing that I found is

https://github.com/unjs/h3/blob/853ae882b58927bbc8eed41b9c02fa7efc529192/src/utils/body.ts#L47

this _rawBody variable is empty on simple tests in a sandbox, but when nuxt-auth sends URLSearchParams object, it is filled. In my sandbox tests, it's not hitting the next if condition and does not throw the error on

https://github.com/unjs/h3/blob/853ae882b58927bbc8eed41b9c02fa7efc529192/src/utils/body.ts#L93

Additional context

No response

Logs

ERROR  [nuxt] [request error] [unhandled] [500] The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received an instance of URLSearchParams
  at Function.from (node:buffer:324:9)  
  at ./node_modules/.pnpm/[email protected]/node_modules/h3/dist/index.mjs:410:21  
  at async readBody (./node_modules/.pnpm/[email protected]/node_modules/h3/dist/index.mjs:438:16)  
  at readBodyForNext (./node_modules/.pnpm/@[email protected][email protected][email protected][email protected]_@[email protected]_reac_wqxp7nydnxshx7noobpzq4kbim/node_modules/@sidebase/nuxt-auth/dist/runtime/server/services/authjs/nuxtAuthHandler.mjs:18:12)  
  at getInternalNextAuthRequestData (./node_modules/.pnpm/@[email protected][email protected][email protected][email protected]_@[email protected]_reac_wqxp7nydnxshx7noobpzq4kbim/node_modules/@sidebase/nuxt-auth/dist/runtime/server/services/authjs/nuxtAuthHandler.mjs:75:18)  
  at Object.handler (./node_modules/.pnpm/@[email protected][email protected][email protected][email protected]_@[email protected]_reac_wqxp7nydnxshx7noobpzq4kbim/node_modules/@sidebase/nuxt-auth/dist/runtime/server/services/authjs/nuxtAuthHandler.mjs:87:25)  
  at async ./node_modules/.pnpm/[email protected]/node_modules/h3/dist/index.mjs:1975:19  
  at async Object.callAsync (./node_modules/.pnpm/[email protected]/node_modules/unctx/dist/index.mjs:72:16)  
  at async toNodeHandle (./node_modules/.pnpm/[email protected]/node_modules/h3/dist/index.mjs:2266:7)  
  at async ufetch (./node_modules/.pnpm/[email protected]/node_modules/unenv/runtime/fetch/index.mjs:9:17)

alimozdemir avatar Aug 23 '24 21:08 alimozdemir

Likely the same issue as #479. Seems to be caused in this part of useAuth.ts, in which a URLSearchParams is thrown in a fetch as request body.

https://github.com/sidebase/nuxt-auth/blob/ae2bf092f73d079a5bf479d13bf0d37db414ee23/src/runtime/composables/authjs/useAuth.ts#L121-L133

Invisi avatar Aug 30 '24 18:08 Invisi

@Invisi Yes exactly, that is the initial point then server tries to parse the body and throws an exception.

alimozdemir avatar Sep 02 '24 21:09 alimozdemir

I experience the same error, I am not able to log out of my application at the moment. Could you maybe treat this bug as a high prio one?

Dunowen avatar Sep 03 '24 17:09 Dunowen

Hi @alimozdemir @Dunowen, I need to confirm why are we providing search params as body. This seemed to work so far, but apparently something changed in one of our dependencies (h3). I will investigate today.

phoenix-ru avatar Sep 05 '24 13:09 phoenix-ru

Is there any update on this?

alimozdemir avatar Sep 10 '24 19:09 alimozdemir

After some investigation, I can confirm that this is a bug with h3 - using URLSearchParams is intended: https://github.com/nextauthjs/next-auth/blob/285cb5ce166bdc1dc99013f0da538186c5be6e0b/packages/next-auth/src/react.tsx#L265-L280

phoenix-ru avatar Sep 12 '24 14:09 phoenix-ru

@phoenix-ru that's why I tested h3 individually for URLSearchParams in a simple code. But it was ok, it was working

alimozdemir avatar Sep 13 '24 06:09 alimozdemir

@alimozdemir Yes, I also tested it individually and URLSearchParams are definitely supported. But what happens here, I guess, is likely _fetch being dispatched to an internal function call instead of HTTP call - that's what I mean by h3 bug.

I will try to create a very minimal reproduction without nuxt-auth and see if that is the case.

phoenix-ru avatar Sep 19 '24 14:09 phoenix-ru

I didn't know what _fetch had internal approach, although good information. Thank you for the headsup

alimozdemir avatar Sep 19 '24 14:09 alimozdemir

Providing an update: I tested the Nuxt starter template and it consistently reproduces there. The only issue is that it seems to be changed in more recent versions of h3, but the starter uses 1.12.0.

Good news is that it seems to be finally patched in their main branch, we just need to wait for a new h3 release and adaptation inside Nuxt: https://github.com/unjs/h3/blob/45cd3cca286c1309a639d1869d0de87411e79209/src/utils/body.ts#L29-L32

phoenix-ru avatar Sep 19 '24 17:09 phoenix-ru

@phoenix-ru I am happy to hear that and hoping it will happen fast causing my app not to run in the production environment. I getting a 500 error saying Invalid URL.

pavewaygroup avatar Sep 20 '24 17:09 pavewaygroup

I don't think it is going to be happening soon, could we have a workaround for this? e.g. disable _fetch internal approach for this call explicitly?

@phoenix-ru

main branch pointing for v2, and it is not clear yet when it is going to be ready. The last release was on Jun 20, even so, nuxt still uses 1.x version

alimozdemir avatar Sep 20 '24 19:09 alimozdemir

Does anyone have a workaround? I can't run it in production environment, error 500.

gabrielws avatar Sep 24 '24 17:09 gabrielws

main branch pointing for v2, and it is not clear yet when it is going to be ready.

You are right about it - I also considered patching this until a new h3 releases. I tried using readFormData but it unfortunately is also broken inside h3 for the same reason [1]. I will try yet again today

upd: It happens on a plain Nuxt server without our module. I will have to create a bug report to h3 and also patch it somehow on our side. The line which causes readFormData to panic: https://github.com/unjs/h3/blob/112fa338ef5bbde6207f3317d2ef51f0e52a59fa/src/utils/body.ts#L296

upd2: Okay, I managed to patch h3 to correctly parse URLSearchParams. I am creating a PR for them

[1]

 ERROR  [nuxt] [request error] [unhandled] [500] The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received an instance of URLSearchParams
  at Function.from (node:buffer:319:9)  
  at ./node_modules/h3/dist/index.mjs:410:21  
  at process.processTicksAndRejections (node:internal/process/task_queues:95:5)  
  at async Object.start (./node_modules/h3/dist/index.mjs:486:26)

phoenix-ru avatar Sep 26 '24 08:09 phoenix-ru

PR created inside h3: https://github.com/unjs/h3/pull/888

phoenix-ru avatar Sep 26 '24 14:09 phoenix-ru

To work I'm change this code in the build

Path: /node_modules/@sidebase/nuxt-auth/dist/runtime/server/services/authjs/nuxtAuthHandler.js Method: createRequestForAuthjs

  let body;
  try {
    body = isMethod(event, ["PATCH", "POST", "PUT", "DELETE"]) ? await readBody(event) : void 0;
  }catch(err){}

joaltoroc avatar Oct 03 '24 23:10 joaltoroc

Thank you @phoenix-ru it got fixed right now.

You have to remove your lock file and node_modules so it will fetch [email protected], then this error just gone.

But unfortunately, it gives me another error "State cookie was missing."

It is really missing in the state cookie, and it's expected somehow. I will do more research before I create another issue in the repo

alimozdemir avatar Oct 04 '24 21:10 alimozdemir