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

Infinite loop on refresh token endpoint in version 9.0.1

Open Suniron opened this issue 1 year ago • 6 comments

Environment


  • Operating System: Linux
  • Node Version: v20.17.0
  • Nuxt Version: 3.13.0
  • CLI Version: 3.13.1
  • Nitro Version: 2.9.7
  • Package Manager: [email protected]
  • Builder: -
  • User Config: -
  • Runtime Modules: -
  • Build Modules: -

Nuxt auth version: 9.0.1

Reproduction

With the configuration below, sign in to the app and reload the page

const determineBaseUrl = () => {
  const baseUrl = process.env.NUXT_PUBLIC_BASE_API || '/api'
  // Check if the baseUrl finishes with a trailing slash
  return !baseUrl.endsWith('/') ? `${baseUrl}/auth/` : `${baseUrl}auth/`
}

const authOptions: ModuleOptions = {
  baseURL: determineBaseUrl(),
  globalAppMiddleware: true,
  isEnabled: true,
  provider: {
    endpoints: {
      getSession: { method: 'get', path: `session` },
      signIn: { method: 'post', path: `credentials` },
      signOut: { method: 'delete', path: `logout` },
      // @ts-expect-error disable sign up, like in the example from the docs: https://auth.sidebase.io/guide/local/quick-start#api-endpoints
      signUp: false,
    },
    pages: {
      login: '/login',
    },
    // See: https://github.com/sidebase/nuxt-auth/issues/867#issuecomment-2293906780
    refresh: {
      endpoint: { method: 'post', path: `refresh-token` },
      isEnabled: true,
      refreshOnlyToken: true,
      token: {
        cookieName: 'auth.refresh',
        httpOnlyCookieAttribute: false,
        maxAgeInSeconds: 2592000,
        refreshRequestTokenPointer: '/refreshToken',
        sameSiteAttribute: 'lax',
        secureCookieAttribute: false,
        signInResponseRefreshTokenPointer: '/refreshToken',
      },
    },
    session: {
      dataType: {
        companyHasAcceptedTermsOfUse: 'boolean',
        companyId: 'number',
        companyName: 'string',
        email: 'string',
        firstName: 'string',
        fullyConnected: 'boolean',
        id: 'string',
        isTwoFactorInitialized: 'boolean',
        lastName: 'string',
        roles: '(\'admin\' | \'member\')[]',
        username: 'string',
      },
    },
    token: {
      cookieName: 'auth.token',
      headerName: 'Authorization',
      httpOnlyCookieAttribute: false,
      maxAgeInSeconds: 15 * 60, // 15 minutes
      sameSiteAttribute: 'lax',
      secureCookieAttribute: false,
      signInResponseTokenPointer: '/accessToken',
    },
    type: 'local',
  },
  sessionRefresh: {
    enableOnWindowFocus: true, // disable to avoid conflicts when switching tabs
    enablePeriodically: 5 * 60 * 1000, // every 5 minutes
  },
}

Describe the bug

The /refresh-token route is called a loop. The first calls refresh the token and then saturate the backend. image

Cookies seems to be correctly set image

Additional context

No response

Logs

No response

Suniron avatar Aug 29 '24 08:08 Suniron

This is caused by your refresh.token.maxAgeInSeconds, which is 2592000.

Transformed in milliseconds by the DefaultRefreshHandler, this means 2_592_000_000, which is higher than the maximum number allowed by JS for setInterval, which is 2_147_483_647. So the value overflows, goes to something negative (or maybe zero?) and the interval is triggered without any pauses.

I fixed this with PR#891 (link).

Meanwhile you can decrease your refresh token maxAge to a value less than 24.8 days or use the temporary package, that contains the fix, until PR#891 is merged & released:

pnpm add https://pkg.pr.new/@sidebase/nuxt-auth@b41f424

cip8 avatar Aug 30 '24 09:08 cip8

Omg thanks for the explanation.

Let me try it the next week 🤞

Suniron avatar Sep 01 '24 15:09 Suniron

Now, I'm trying with a light config as possible (see below) and I haven't the loop on the refresh (thanks!), but the refreshing doesn't work well.

For eg. if I remove the cookie auth.token (and have a valid auth.refresh-token), after reloading the page, I'm redirected to the login page instead of having a new refreshed token set...

Also, I disabled the SSR to have a good view of what happened and I don't see any request to refresh the token... 😓 (this test was OK with the 0.8.2 version)

I tried a trick to refresh the token when a refresh-token is detected but the (access) token not:

const { refresh } = useAuth()

const refreshCookie = useCookie('auth.refresh-token')
const tokenCookie = useCookie('auth.token')

// If needed, refresh the token
if (refreshCookie.value && !tokenCookie.value) {
    await refresh()
}

The behavior is strange because I can log the refreshCookie cookie (which is valid) but a null value is sent to my refresh route... image

Configuration

const authOptions: ModuleOptions = {
  baseURL: determineBaseUrl(),
  globalAppMiddleware: true,
  isEnabled: true,
  provider: {
    endpoints: {
      getSession: { method: 'get', path: `session` },
      signIn: { method: 'post', path: `credentials` },
      signOut: { method: 'delete', path: `logout` },
      // @ts-expect-error disable sign up, like in the example from the docs: https://auth.sidebase.io/guide/local/quick-start#api-endpoints
      signUp: false,
    },
    pages: {
      login: '/login',
    },
    // See: https://github.com/sidebase/nuxt-auth/issues/867#issuecomment-2293906780
    refresh: {
      endpoint: { method: 'post', path: `refresh-token` },
      isEnabled: true,
    },
    session: {
      dataType: {
        companyHasAcceptedTermsOfUse: 'boolean',
        companyId: 'number',
        companyName: 'string',
        email: 'string',
        firstName: 'string',
        fullyConnected: 'boolean',
        id: 'string',
        isTwoFactorInitialized: 'boolean',
        lastName: 'string',
        roles: '(\'admin\' | \'member\')[]',
        username: 'string',
      },
    },
    token: {
      signInResponseTokenPointer: '/accessToken',
    },
    type: 'local',
  },
}

Suniron avatar Sep 02 '24 15:09 Suniron

Numbers are in milliseconds. Seems like a typo... A temporary fix for me was to multiply numbers to 60 * 1000

abzarak avatar Sep 03 '24 04:09 abzarak

Now, I'm trying with a light config as possible (see below) and I haven't the loop on the refresh (thanks!), but the refreshing doesn't work well.

For eg. if I remove the cookie auth.token (and have a valid auth.refresh-token), after reloading the page, I'm redirected to the login page instead of having a new refreshed token set...

Indeed, this happens to me too: when the refresh token is there and the auth one is missing, the refresh will not be triggered.

The error seems to be related to the refresh-token server this time - I will propose a fix soon.

cip8 avatar Sep 06 '24 08:09 cip8

@Suniron the second part of the issue will be fixed by PR#902.

cip8 avatar Sep 06 '24 10:09 cip8