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.31k stars 164 forks source link

Infinite loop after upgrading to Nuxt 3.12.4 (callbackUrl) #832

Closed alimozdemir closed 2 months ago

alimozdemir commented 3 months ago

Environment

Reproduction

How can I put a reproduction for real-life implementation e.g. with a real provider?

Describe the bug

I'm using nuxt auth via azure-ad and after I upgraded nuxt to 3.12.4 it started an infinite loop with vue-router.

It keeps giving following warning with adding more urls to end of it. (see the logs section)


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

async function refreshAccessToken(accessToken: any) {
  try {
      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 {
          ...accessToken,
          accessToken: res.access_token,
          accessTokenExpires: Date.now() + res.expires_in * 1000,
          refreshToken: res.refresh_token ?? accessToken.refreshToken, // Fall back to old refresh token
      };
  } catch (error) {
      console.log(error);
      return {
          ...accessToken,
          error: 'RefreshAccessTokenError',
      };
  }
}

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 }) {
      // Persist the access_token in the encrypted JWT.
      if (account && profile) {
        if (account.access_token) {
          const decoded = decode(account.access_token)
          token.accessToken = account.access_token;
          if (decoded && typeof decoded !== 'string')
            token.roles = decoded.roles;
        }
        if (account.expires_at)
          token.accessTokenExpires = account.expires_at * 1000;

        token.refreshToken = account.refresh_token;
      }

      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 profile {{myScope}}`,
        },
      },
    })

  ]
})

nuxt.config.ts

  auth: { 
    isEnabled: true,
    provider: {
      type: "authjs",
      defaultProvider: "azure-ad"
    },
    globalAppMiddleware: {
      isEnabled: true,
      addDefaultCallbackUrl: '/'
    },
    sessionRefresh: {
        enablePeriodically: 30000,
        enableOnWindowFocus: true,
    }
 },

callbackUrl is not working in my case also. I'm not sure why, I couldn't find the time to investigate either but It looks like after upgrade this callback logic causes this problem.

Let me know if you need more info, sorry for the reproduce, I can't do that with azure-ad

Additional context

I did a clean upgrade, nothing got changed, when I rollback it to 3.12.3 it works.

Logs

WARN  [Vue Router warn]: No match found for location with path "/api/auth/session?callbackUrl=http:%2F%2Flocalhost%2Fapi%2Fauth%2Fsession?callbackUrl=http:%25252F%25252Flocalhost%25252Fapi%25252Fauth%25252Fsession?callbackUrl=http:%252525252F%252525252Flocalhost%252525252Fapi%252525252Fauth%252525252Fsession?callbackUrl=http:%2525252525252F%2525252525252Flocalhost%2525252525252Fapi%2525252525252Fauth%2525252525252Fsession?callbackUrl=http:%25252525252525252F%25252525252525252Flocalhost%25252525252525252Fapi%25252525252525252Fauth%25252525252525252Fsession?callbackUrl=http:%252525252525252525252F%252525252525252525252Flocalhost%252525252525252525252Fapi%252525252525252525252Fauth%252525252525252525252Fsession?callbackUrl=http:%2525252525252525252525252F%2525252525252525252525252Flocalhost%2525252525252525252525252Fapi%2525252525252525252525252Fauth%2525252525252525252525252Fsession?callbackUrl=http:%25252525252525252525252525252F%25252525252525252525252525252Flocalhost%25252525252525252525252525252Fapi%25252525252525252525252525252Fauth%25252525252525252525252525252Fsession?callbackUrl=http:%252525252525252525252525252525252F%252525252525252525252525252525252Flocalhost%252525252525252525252525252525252Fapi%252525252525252525252525252525252Fauth%252525252525252525252525252525252Fsession?callbackUrl=http:%2525252525252525252525252525252525252F%2525252525252525252525252525252525252Flocalhost%2525252525252525252525252525252525252Fapi%2525252525252525252525252525252525252Fauth%2525252525252525252525252525252525252Fsession?callbackUrl=http:%25252525252525252525252525252525252525252F%25252525252525252525252525252525252525252Flocalhost%25252525252525252525252525252525252525252Fapi%25252525252525252525252525252525252525252Fauth%25252525252525252525252525252525252525252Fsession?callbackUrl=http:%252525252525252525252525252525252525252525252F%252525252525252525252525252525252525252525252Flocalhost%252525252525252525252525252525252525252525252Fapi%252525252525252525252525252525252525252525252Fauth%252525252525252525252525252525252525252525252Fsession?callbackUrl=http:%2525252525252525252525252525252525252525252525252F%2525252525252525252525252525252525252525252525252Flocalhost%2525252525252525252525252525252525252525252525252Fapi%2525252525252525252525252525252525252525252525252Fauth%2525252525252525252525252525252525252525252525252Fsession?callbackUrl=http:%25252525252525252525252525252525252525252525252525252F%25252525252525252525252525252525252525252525252525252Flocalhost%25252525252525252525252525252525252525252525252525252Fapi%25252525252525252525252525252525252525252525252525252Fauth%25252525252525252525252525252525252525252525252525252Fsession?callbackUrl=http:%252525252525252525252525252525252525252525252525252525252F%252525252525252525252525252525252525252525252525252525252Flocalhost%252525252525252525252525252525252525252525252525252525252Fapi%252525252525252525252525252525252525252525252525252525252Fauth%252525252525252525252525252525252525252525252525252525252Fsession?callbackUrl=http:%2525252525252525252525252525252525252525252525252525252525252F%2525252525252525252525252525252525252525252525252525252525252Flocalhost%2525252525252525252525252525252525252525252525252525252525252Fapi%2525252525252525252525252525252525252525252525252525252525252Fauth%2525252525252525252525252525252525252525252525252525252525252Fsession?callbackUrl=http:%25252525252525252525252525252525252525252525252525252525252525252F%25252525252525252525252525252525252525252525252525252525252525252Flocalhost%25252525252525252525252525252525252525252525252525252525252525252Fapi%25252525252525252525252525252525252525252525252525252525252525252Fauth%25252525252525252525252525252525252525252525252525252525252525252Fsession?callbackUrl=http:%252525252525252525252525252525252525252525252525252525252525252525252F%252525252525252525252525252525252525252525252525252525252525252525252Flocalhost:3000%252525252525252525252525252525252525252525252525252525252525252525252F"
n-rowe commented 3 months ago

I've got the same issue using the azure-ad-b2c provider. Downgrading to the previous nuxt version (3.12.3) did not resolve this issue for me,

import AzureB2CProfile from 'next-auth/providers/azure-ad-b2c'
import { NuxtAuthHandler } from '#auth'

export default NuxtAuthHandler({
  secret: useRuntimeConfig().auth.secret,
  providers: [
    AzureB2CProfile({
      authorization: { params: { scope: 'openid offline_access' } },
      checks: ['pkce'],
      clientId: useRuntimeConfig().auth.clientId,
      clientSecret: useRuntimeConfig().auth.clientSecret, // unused
      client: { response_types: ['code'] },
      primaryUserFlow: useRuntimeConfig().auth.primaryUserFlow,
      tenantId: useRuntimeConfig().auth.tenantName,
    }),
  ],
  callbacks: {
    jwt({ token, account }) {
      if (account)
        token.accessToken = account.access_token

      return token
    },
  },
})

Logs

I receive the same warning from Vue Router

 WARN  [Vue Router warn]: No match found for location with path "/auth/signin/azure-ad-b2c"

I also receive a stack trace from what seems to be h3

 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 /D:/sidebase-test/node_modules/.pnpm/h3@1.12.0/node_modules/h3/dist/index.mjs:410:21
  at async readBody (/D:/sidebase-test/node_modules/.pnpm/h3@1.12.0/node_modules/h3/dist/index.mjs:438:16)
  at readBodyForNext (D:\sidebase-test\node_modules\.pnpm\@sidebase+nuxt-auth@0.8.1_encoding@0.1.13_magicast@0.3.4_next-auth@4.21.1_next@13.5.6_@babel+_znrr33m56h2fg2wfir25brkt2i\node_modules\@sidebase\nuxt-auth\dist\runtime\server\services\authjs\nuxtAuthHandler.mjs:18:12)
  at getInternalNextAuthRequestData (D:\sidebase-test\node_modules\.pnpm\@sidebase+nuxt-auth@0.8.1_encoding@0.1.13_magicast@0.3.4_next-auth@4.21.1_next@13.5.6_@babel+_znrr33m56h2fg2wfir25brkt2i\node_modules\@sidebase\nuxt-auth\dist\runtime\server\services\authjs\nuxtAuthHandler.mjs:75:18)
  at Object.handler (D:\sidebase-test\node_modules\.pnpm\@sidebase+nuxt-auth@0.8.1_encoding@0.1.13_magicast@0.3.4_next-auth@4.21.1_next@13.5.6_@babel+_znrr33m56h2fg2wfir25brkt2i\node_modules\@sidebase\nuxt-auth\dist\runtime\server\services\authjs\nuxtAuthHandler.mjs:87:25)
  at async /D:/sidebase-test/node_modules/.pnpm/h3@1.12.0/node_modules/h3/dist/index.mjs:1975:19
  at async Object.callAsync (/D:/sidebase-test/node_modules/.pnpm/unctx@2.3.1/node_modules/unctx/dist/index.mjs:72:16)
  at async toNodeHandle (/D:/sidebase-test/node_modules/.pnpm/h3@1.12.0/node_modules/h3/dist/index.mjs:2266:7)
  at async ufetch (/D:/sidebase-test/node_modules/.pnpm/unenv@1.10.0/node_modules/unenv/runtime/fetch/index.mjs:9:17)

Resolution/Workarounds

I dug and created a patch where I changed the call to $fetch to use fetch for this line: https://github.com/sidebase/nuxt-auth/blob/9295d1fe4f27d42c879862a48202f5b215fc0048/src/runtime/utils/fetch.ts#L12

The errors are now gone and it seems to redirect me correctly

phoenix-ru commented 3 months ago

Thank you very much for reporting this issue! I bumped into the same one when trying to migrate to a new Authjs (which is not ready yet) and spent quite some time trying to pinpoint the culprit - in my case it was pages and I was getting the exact same error. I was blaming @auth/core for the issue, but judging by your experience, it might be caused by Nuxt 3.12.4

@alimozdemir What version of next-auth do you have? You can check via pnpm why next-auth

@n-rowe I am yet not sure if you experience the same issue as you seem to not be getting an insanely long error message as the OP?

n-rowe commented 3 months ago

@n-rowe I am yet not sure if you experience the same issue as you seem not to be getting an insanely long error message as the OP?

I'm unsure if it's the same issue, I have the same WARN the OP gets, and I'm experiencing the infinite loop from vue-router. Though our resolutions are slightly different - I may have just downgraded Nuxt incorrectly.

I happened to copy the Vue Router warn at the very beginning but I am experiencing the same sort of error with it just continuously appending onto itself.

phoenix-ru commented 3 months ago

I happened to copy the Vue Router warn at the very beginning but I am experiencing the same sort of error with it just continuously appending onto itself.

Thank you for clarifying, it may actually be related to $fetch implementation being changed in 3.12.4, I will check their commits

edit: couldn't find the reason after looking at release notes :slightly_frowning_face:

@zoey-kaiser Are you able to reproduce it by updating to Nuxt 3.12.4 on an internal project?

n-rowe commented 3 months ago

Thank you for clarifying, it may actually be related to $fetch implementation being changed in 3.12.4, I will check their commits

Changing $fetch didn't completely resolve my issue as I was still experiencing problems on the server side. I changed it back to $fetch and noticed that the URL didn't include the host on the server.

I patched these lines of code https://github.com/sidebase/nuxt-auth/blob/3da2724d5bfe8af3aaecdb1671db091bcb29cd91/src/runtime/utils/url.ts#L13-L17

Into

return joinURL(authStateInternal.baseURL, path)

and now I'm experiencing no errors, and everything is working as I expected. Either way, I think it is an issue around the fetching/URL.

alimozdemir commented 3 months ago

I'm not sure if it is related or not but with my configurations above, callbackUrl does not work at all, it always redirects back to addDefaultCallbackUrl (with working version nuxt v3.12.3)

(note, if I don't set addDefaultCallbackUrl, It just redirects to http://localhost:3000/auth/signIn)

http://localhost:3000/auth/signIn?callbackUrl=http://localhost:3000/dashboard&error=undefined

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

image

Maybe I will raise another issue for this once the infinite loop is fixed.

alimozdemir commented 3 months ago

Thank you very much for reporting this issue! I bumped into the same one when trying to migrate to a new Authjs (which is not ready yet) and spent quite some time trying to pinpoint the culprit - in my case it was pages and I was getting the exact same error. I was blaming @auth/core for the issue, but judging by your experience, it might be caused by Nuxt 3.12.4

@alimozdemir What version of next-auth do you have? You can check via pnpm why next-auth

@n-rowe I am yet not sure if you experience the same issue as you seem to not be getting an insanely long error message as the OP?

Hi @phoenix-ru , sorry, I just saw your message.

"next-auth": "4.21.1"

dependencies:
next-auth 4.21.1

devDependencies:
@sidebase/nuxt-auth 0.8.1
└── next-auth 4.21.1 peer
zoey-kaiser commented 3 months ago

@zoey-kaiser Are you able to reproduce it by updating to Nuxt 3.12.4 on an internal project?

I have not been able to reproduce the issue with the infinite call loops inside my test application. However, I have been able tp reproduce the issue of the defaultCallbackURL not working correctly. I opened a new issue for this here to ensure that we can investigate both errors separately: #857

alimozdemir commented 2 months ago

Hi again,

I just did another test with a clean install and upgraded the nuxt + nuxt-auth, but the issue still persists.

Is there any workaround for it?

@n-rowe did you figure out a workaround without patching the package locally?

n-rowe commented 2 months ago

@n-rowe did you figure out a workaround without patching the package locally?

I also upgraded the package and took another crack at figuring out the problem,

I had some code that ran in a fetch which detected if there were any 401 errors, it would then try to sign the user back in which was causing an infinite loop of redirects and it saying it can't update the headers.

I also had a custom AUTH_ORIGIN which was causing me some issues, I've updated my env file to have

NUXT_AUTH_ORIGIN=http://localhost:3000

and my nuxt.config.ts:

auth: {
  baseURL: `${process.env.BASE_URL ?? process.env.NUXT_AUTH_ORIGIN}/auth`,
  originEnvKey: 'NUXT_AUTH_ORIGIN',
  provider: {
    type: 'authjs',
    defaultProvider: 'azure-ad-b2c',
  },
},

This is stable for me right now and working pretty well

alimozdemir commented 2 months ago

I can see that you're using a different provider than I use

you're using azure-ad-b2c, and I'm using azure-ad. That might be the real reason for the issue maybe.

btw, I tried custom origin as well but didn't work tho

alimozdemir commented 2 months ago

I found the issue. It is related to server folder of nuxt. I think it is also related to https://github.com/nuxt/nuxt/issues/27780.

Here is an example

https://stackblitz.com/edit/github-cu5cxv?file=app%2Fapp.vue,package.json

You can see both endpoints are working.

When I upgrade it to 3.12.4, the first endpoint does not work anymore.

https://stackblitz.com/edit/github-cu5cxv-bqkc23?file=package.json

When I moved my server folder to the up of app folder, the problem was fixed. Hence closing the issue!

Thank you for your help!