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

Infinite loop on refresh token endpoint in version 9.0.1 #890

Open Suniron opened 2 weeks ago

Suniron commented 2 weeks ago

Environment


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

cip8 commented 2 weeks ago

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

Omg thanks for the explanation.

Let me try it the next week 🤞

Suniron commented 1 week ago

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',
  },
}
abzarak commented 1 week ago

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

cip8 commented 1 week ago

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

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