sidebase / nuxt-auth

Authentication built for Nuxt 3! Easily add authentication via OAuth providers, credentials or Email Magic URLs!
https://auth.sidebase.io
MIT License
1.24k stars 161 forks source link

500 Received an instance of URLSearchParams #883

Open alimozdemir opened 3 weeks ago

alimozdemir commented 3 weeks ago

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/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)
Invisi commented 2 weeks ago

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

alimozdemir commented 1 week ago

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

Dunowen commented 1 week ago

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 commented 1 week ago

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 commented 4 days ago

Is there any update on this?

phoenix-ru commented 2 days ago

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 commented 2 days ago

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