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

500 Received an instance of URLSearchParams #883

Open
alimozdemir opened this issue Aug 23, 2024 · 16 comments
Open

500 Received an instance of URLSearchParams #883

alimozdemir opened this issue Aug 23, 2024 · 16 comments
Labels
bug A bug that needs to be resolved p4 Important Issue

Comments

@alimozdemir
Copy link

Environment

  • Operating System: Darwin
  • Node Version: v20.11.1
  • Nuxt Version: 3.12.4
  • CLI Version: 3.13.0
  • Nitro Version: 2.9.7
  • Package Manager: pnpm@8.15.4
  • Builder: -
  • User Config: modules, extends, future, css, app, runtimeConfig, auth, compatibilityDate
  • Runtime Modules: @nuxtjs/tailwindcss@6.12.1, @nuxt/fonts@0.7.2, @vueuse/nuxt@10.11.1, @sidebase/nuxt-auth@0.8.2
  • Build Modules: -

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

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/h3@1.12.0/node_modules/h3/dist/index.mjs:410:21  
  at async readBody (./node_modules/.pnpm/h3@1.12.0/node_modules/h3/dist/index.mjs:438:16)  
  at readBodyForNext (./node_modules/.pnpm/@sidebase+nuxt-auth@0.8.2_magicast@0.3.4_next-auth@4.21.1_next@13.5.6_@babel+core@7.25.2_reac_wqxp7nydnxshx7noobpzq4kbim/node_modules/@sidebase/nuxt-auth/dist/runtime/server/services/authjs/nuxtAuthHandler.mjs:18:12)  
  at getInternalNextAuthRequestData (./node_modules/.pnpm/@sidebase+nuxt-auth@0.8.2_magicast@0.3.4_next-auth@4.21.1_next@13.5.6_@babel+core@7.25.2_reac_wqxp7nydnxshx7noobpzq4kbim/node_modules/@sidebase/nuxt-auth/dist/runtime/server/services/authjs/nuxtAuthHandler.mjs:75:18)  
  at Object.handler (./node_modules/.pnpm/@sidebase+nuxt-auth@0.8.2_magicast@0.3.4_next-auth@4.21.1_next@13.5.6_@babel+core@7.25.2_reac_wqxp7nydnxshx7noobpzq4kbim/node_modules/@sidebase/nuxt-auth/dist/runtime/server/services/authjs/nuxtAuthHandler.mjs:87:25)  
  at async ./node_modules/.pnpm/h3@1.12.0/node_modules/h3/dist/index.mjs:1975:19  
  at async Object.callAsync (./node_modules/.pnpm/unctx@2.3.1/node_modules/unctx/dist/index.mjs:72:16)  
  at async toNodeHandle (./node_modules/.pnpm/h3@1.12.0/node_modules/h3/dist/index.mjs:2266:7)  
  at async ufetch (./node_modules/.pnpm/unenv@1.10.0/node_modules/unenv/runtime/fetch/index.mjs:9:17)
@alimozdemir alimozdemir added bug A bug that needs to be resolved pending An issue waiting for triage labels Aug 23, 2024
@Invisi
Copy link

Invisi commented Aug 30, 2024

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.

const body = new URLSearchParams({
...options,
csrfToken,
callbackUrl,
json: true
})
const fetchSignIn = () => _fetch<{ url: string }>(nuxt, `/${action}/${provider}`, {
method: 'post',
params: authorizationParams,
headers,
body
}).catch<Record<string, any>>((error: { data: any }) => error.data)

@alimozdemir
Copy link
Author

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

@Dunowen
Copy link

Dunowen commented Sep 3, 2024

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?

@phoenix-ru
Copy link
Collaborator

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.

@alimozdemir
Copy link
Author

Is there any update on this?

@phoenix-ru phoenix-ru added p4 Important Issue and removed pending An issue waiting for triage labels Sep 12, 2024
@phoenix-ru
Copy link
Collaborator

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

@alimozdemir
Copy link
Author

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

@phoenix-ru
Copy link
Collaborator

@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.

@alimozdemir
Copy link
Author

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

@phoenix-ru
Copy link
Collaborator

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

@pavewaygroup
Copy link

@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.

@alimozdemir
Copy link
Author

alimozdemir commented Sep 20, 2024

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

@gabrielws
Copy link

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

@phoenix-ru
Copy link
Collaborator

phoenix-ru commented Sep 26, 2024

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
Copy link
Collaborator

PR created inside h3:
unjs/h3#888

@joaltoroc
Copy link

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){}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A bug that needs to be resolved p4 Important Issue
Projects
None yet
Development

No branches or pull requests

7 participants