nextjs-auth0 icon indicating copy to clipboard operation
nextjs-auth0 copied to clipboard

v4: Infinitely stacking cookies

Open mvvmm opened this issue 10 months ago • 59 comments

Checklist

  • [x] The issue can be reproduced in the nextjs-auth0 sample app (or N/A).
  • [x] I have looked into the Readme, Examples, and FAQ and have not found a suitable solution or answer.
  • [x] I have looked into the API documentation and have not found a suitable solution or answer.
  • [x] I have searched the issues and have not found a suitable solution or answer.
  • [x] I have searched the Auth0 Community forums and have not found a suitable solution or answer.
  • [x] I agree to the terms within the Auth0 Code of Conduct.

Description

  • v4 sdk creates a new additional transactional cookie each time the unauthenticated user navigates to the app
  • v4 logout does not remove said cookies

v3 does not create infinite cookies and does remove cookies on logout.

This eventually leads to a situation where the header of the request is too large.

Reproduction

  1. Be unauthenticated in your application (remove all cookies on that domain if you want)
  2. Navigate to a route in your application (receive a cookie)
  3. Navigate to another route in your application (receive another cookie)
  4. Repeat as many times as you wish
  5. Navigate to /auth/logout (receive another cookie, and cookies are not removed)

https://github.com/user-attachments/assets/21076f33-3c81-428b-b84b-4d566883cb4e

Additional context

N/A

nextjs-auth0 version

4.0

Next.js version

15.1.6

Node.js version

20.x

mvvmm avatar Feb 13 '25 21:02 mvvmm

Thank you for reporting this, we are actively looking into this. We will post an update here once we have found a resolution.

tusharpandey13 avatar Feb 14 '25 10:02 tusharpandey13

We had some user reports of this in production. Sharing this hacky fix until there is a patch available. This is a snippet from our middleware function:

const reqCookieNames = req.cookies.getAll().map((cookie) => cookie.name);

const authRes = await auth0.middleware(req);
if (req.nextUrl.pathname === '/auth/login') {
    // This is a workaround for this issue: https://github.com/auth0/nextjs-auth0/issues/1917
    // The auth0 middleware sets some transaction cookies that are not deleted after the login flow completes.
    // This causes stale cookies to be used in subsequent requests and eventually causes the request header to be rejected because it is too large.
    reqCookieNames.forEach((cookie) => {
      if (cookie.startsWith('__txn')) {
        authRes.cookies.delete(cookie);
      }
    });
  }

if (req.nextUrl.pathname.startsWith('/auth')) {
    // If the request is for the auth routes, short circuit the middleware chain and return the response
    return authRes;
 }

On /auth/login it will delete any stale __txn cookies before creating a new one. Important to get the existing cookies before you apply the auth0.middleware or else you will end up deleting the __txn cookie for the current login attempt.

jdwitten avatar Feb 23 '25 17:02 jdwitten

We had some user reports of this in production. Sharing this hacky fix until there is a patch available. This is a snippet from our middleware function:

const reqCookieNames = req.cookies.getAll().map((cookie) => cookie.name);

const authRes = await auth0.middleware(req);
if (req.nextUrl.pathname === '/auth/login') {
    // This is a workaround for this issue: https://github.com/auth0/nextjs-auth0/issues/1917
    // The auth0 middleware sets some transaction cookies that are not deleted after the login flow completes.
    // This causes stale cookies to be used in subsequent requests and eventually causes the request header to be rejected because it is too large.
    reqCookieNames.forEach((cookie) => {
      if (cookie.startsWith('__txn')) {
        authRes.cookies.delete(cookie);
      }
    });
  }

if (req.nextUrl.pathname.startsWith('/auth')) {
    // If the request is for the auth routes, short circuit the middleware chain and return the response
    return authRes;
 }

On /auth/login it will delete any stale __txn cookies before creating a new one. Important to get the existing cookies before you apply the auth0.middleware or else you will end up deleting the __txn cookie for the current login attempt.

THX @jdwitten . We faced a same problem while handling 401 in middleware. This workaround saved us 🍺

vjekofalco avatar Mar 10 '25 11:03 vjekofalco

Trying to migrate von v3 to v4 and immediately encountered this problem. Only had problems with the migration and now the app is breaking for users because of this. I wished this would be treated with a higher priority.

Strernd avatar Mar 19 '25 14:03 Strernd

Indeed - we have a migration branch and every time we deploy it we immediately start hitting 413s. We can manually wipe our cookies and things seem OK, but obviously we aren't expecting our users to do this. This is a blocking problem on the migration path, @tusharpandey13 have you folks had any luck isolating the root cause here? We can't upgrade nextJS until v4 of this SDK is working more reliably.

ross-mcnairn-dev avatar Mar 25 '25 11:03 ross-mcnairn-dev

Any more updates/info from the team or issue participants on this one? I'm currently doing a v4 migration and the reports here make me quite nervous.

I haven't been able to reproduce in our preview environment but feels like it'll be sods law that this hits customers if rolled out to production. Tempted to put in a hack to clean up transaction cookies like @jdwitten suggested to be on the safe side in lieu of an official response.

cbovis avatar Apr 24 '25 00:04 cbovis

Still an issue in 4.4.2

marc-wilson avatar Apr 24 '25 17:04 marc-wilson

https://github.com/auth0/nextjs-auth0/pull/2077 is open to address and fix this issue. Expect a release to be made shortly.

tusharpandey13 avatar May 02 '25 09:05 tusharpandey13

My team is experiencing this issue after upgrading to v4. We're currently on '^4.6.1' and using custom routes to keep the v3 paths of /api/auth/*.

 const auth0 = new Auth0Client({
    ...
    routes: {
      login: '/api/auth/login',
      logout: '/api/auth/logout',
      callback: '/api/auth/callback',
      backChannelLogout: '/api/auth/backchannel-logout',
    },
  })

I've added the middleware workaround outlined above but we're still seeing 431s.

// middleware.ts

  const reqCookieNames = request.cookies.getAll().map((cookie) => cookie.name)
  if (request.nextUrl.pathname === '/api/auth/login') {
    // This is a workaround for this issue: https://github.com/auth0/nextjs-auth0/issues/1917
    // The auth0 middleware sets some transaction cookies that are not deleted after the login flow completes.
    // This causes stale cookies to be used in subsequent requests and eventually causes the request header to be rejected because it is too large.
    reqCookieNames.forEach((cookie) => {
      if (cookie.startsWith('__txn')) {
        authResponse.cookies.delete(cookie)
      }
    })
  }

  // if path starts with /auth, let the auth middleware handle it
  if (
    pathname.startsWith('/auth') ||
    pathname.startsWith('/api/auth') ||
    pathname.startsWith('/v2/logout')
  ) {
    return authResponse
  }

  const session = await auth0.getSession(request)

  if (!session && pathname !== '/') {
    const { origin } = new URL(request.url)
    return NextResponse.redirect(
      `${origin}/api/auth/login?returnTo=${pathname}`
    )
  }

Maybe my middleware setup is bunk but I'm out of ideas.

nnnnat avatar Jun 27 '25 16:06 nnnnat

Re-opening this issue since customers are facing this again. We will actively patch this and make a release soon.

tusharpandey13 avatar Jul 06 '25 19:07 tusharpandey13

@tusharpandey13 do you have an ETA on a revised fix for this? We're seeing random logout issues for some of our users recently which I suspect is related to v4 upgrade. Haven't been able to identify a root cause but this seems like it could be a potential reason.

cbovis avatar Jul 10 '25 09:07 cbovis

Hi @cbovis, you can expect a minor/patch release that fixes this by 17th July (next thursday), this has been prioritized and we'll try to push it sooner. Thanks!

tusharpandey13 avatar Jul 10 '25 11:07 tusharpandey13

To update what we're seeing it does look like the workaround for the __txn cookie is preventing the 413 errors but it seems like it's also causing the The state parameter is invalid. issue that other people have seen. So far I think it's only happening when you have the app open in multiple tabs, one tab updates correctly while the other fails because the __txn cookie was deleted by the middleware.

nnnnat avatar Jul 14 '25 20:07 nnnnat

@tusharpandey13 Curious if you can provide a status for the fix for this critical bug? it is a deal breaker for us and we'll need to use an alternate vendor if it's not addressed this week.

jgeurts avatar Jul 15 '25 15:07 jgeurts

What I am curious to understand is what is causing these infinite stacking cookies, can you elaborate @jgeurts ? I see some inaccurate information in this thread, such as that the transaction cookie is not removed after logging in, which isn't entirely accurate (see here), let me elaborate:

  • Whenever you start the login process, we set a unique transaction cookie.
  • If the login is completed, and you are logged in. That unique transaction cookie is deleted.

If the above is not happening, that is definitely a bug. But important in this case is to look at the single, unique cookie in isolation here.

What we do need to solve:

- If the login is completed with an error, and you are not logged in, we should delete the transaction cookie.

What does not happen is:

  • When login for transaction A is done, we do not clear any other transaction cookie.
  • When login for transaction A is started, but never completed (e.g. by clicking back), we never delete the transaction cookie.

The above is expected because v4 supports multiple transactions, clearing any unrelated transaction cookies will break that functionality. Instead, we rely on the cookies expiring after 1 hour and self-cleaning after an hour. Are you saying this is not happening?

Based on my understanding, the transaction cookies can only stack infinitly if you:

  • Keep clicking login and back
  • Have an infinite redirect between your Login endpoint and Auth0.

Both are not expected scenario's and the second one should be solved differently.

That said, that does not mean we want to better understand what is going on, and even improve where we can. Can you help us understand?

Some solutions I can think of is to add a configuration options to the SDK that allows either:

  • Only use a single transaction cookie
  • Configure the expiration of the transaction cookie
  • Configure a max amount of transaction cookies

The latter won't resolve the problem, but would reduce the timespan the cookies exist, reducing the chance for them to grow.

frederikprijck avatar Jul 16 '25 11:07 frederikprijck

@frederikprijck I don't have a great answer for you, unfortunately. This week, we had a two-day event where we had roughly 400 people signing up for our web application. Over the course of the two days, around 10% of those people experienced cookies stacking to the point where they started getting 431 errors from request header being too large. Generally speaking, the users were not logging out/in but a decent number of errors happened the morning of day 2 of the event. The frustrating part for them was that the site worked for a period of time and then seemingly randomly locked them out with a 431.

We've pushed out the transaction cookie hack in an attempt to fix things but confidence is low that it's the "right" fix.

Curious why a unique transaction cookie name is necessary vs reusing the same name? I think that could solve the issue, even if the cookie doesn't clear properly.

jgeurts avatar Jul 16 '25 21:07 jgeurts

Curious why a unique transaction cookie name is necessary vs reusing the same name? I think that could solve the issue, even if the cookie doesn't clear properly.

In v3, that is how it worked (a single transaction cookie). in v4, this was changed. I believe I agree with u about it solving your issue at hand, but that would be a breaking change. That's why I think adding an opt-in flag to move back to a single transaction could solve the issue, and I am happy to push for that.

However, I also believe this does not solve the underlying issue and it would make sense to understand that. But it would unblock you and we should be able to provide that configuration value prior to knowing the actual cause.

frederikprijck avatar Jul 17 '25 07:07 frederikprijck

Curious why a unique transaction cookie name is necessary vs reusing the same name? I think that could solve the issue, even if the cookie doesn't clear properly.

The unique transaction cookie solves the following issue:

  • User opens your App in Tab 1 and clicks Login (This sets a transaction cookie), gets redirected to Auth0 and does not finish logging in.
  • User opens your App in Tab 2 and clicks Login (This overrides the transaction cookie), same as above they do not finish logging in.
  • User goes back to Tab 1 and finishes logging in, when being redirect back to your app they get "Invalid State" because the transaction in the cookie if for Tab 2, and Tab 1 is overridden so the transaction data is lost.

This is solved by using a unique transaction cookie for each transaction that was started, but with its own consequences.

So it is important to know that once we would bring back a single transaction cookie in an opt-in way, you will be open for the above behavior again, which is also why I think it is important to find the actual root cause of the cookies stacking, and not the fact that they aren't clearing, because I do think using a unique cookie per transaction should be the better configuration.

frederikprijck avatar Jul 17 '25 08:07 frederikprijck

any update on when a fix will be available?

marc-wilson avatar Jul 24 '25 22:07 marc-wilson

Hi 👋 Following PRs are open to address these issues:

#2244 : Add flag to control parallel transactions, ensure txn cookies are deleted on callback error as well as success. #2245 : Allow configuring transaction cookie maxAge.

By using a combination of these 2 features, the chances of getting a 413 will be reduced and it will be easier to figure out the root cause of the redirections.

Transaction cookies are created everytime startInteractiveLogin (called by handleLogin) redirects for login. These cookies are deleted once the callback handler handleCallback either succeeds or throws and error for the specific transaction state for which it was called.

These transaction cookies are NOT deleted when the callback handler is never called (the user navigates away from the login form (user manually doing this, or a misconfigured setup that ends up in an infinite redirect), in which case the cookies get automatically deleted a/c to their maxAge. If enough of these login attempts are cancelled before the txn cookie maxage, cookie storage can fill up.

Please adjust maxAge a/c to the needs of your application, do you expect users to have the login form open for an hour? If not, it's better to reduce it to, say 15 minutes.

It is important to note that the above will not be of much use if there is a misconfiguration in setup that's causing an infinite redirect b/w app and the login page. In that case, if your application does not need to support parallel transactions (multiple login attempts in multiple tabs of the same browser at the same time), use enableParallelTransactions = false to restrict the app to use a single transaction cookie. If one login transaction is active, trying to login again will fail with a warning (no throw).

If your app needs multiple parallel transacitons with a maxAge of multiple minutes and you are experiencing 413 errors, finding the root cause of what's causing the transaction cookie stacking is advised.

I do think using a unique cookie per transaction should be the better configuration.

V3 supported a single transaction cookie per browser window so most users can do with just setting enableParallelTransactions = false.

We understand that this is not a complete solution to this problem but we believe these changes will help us get closer to the actual root cause of this issue.

Thanks to everyone who provided traces, work-arounds, and patience while we tracked this down! 🙏 , we will actively follow up on this.

tusharpandey13 avatar Jul 25 '25 13:07 tusharpandey13

v4.9.0 was released which contains both of these PRs: npm: https://www.npmjs.com/package/@auth0/nextjs-auth0/v/4.9.0 github: https://github.com/auth0/nextjs-auth0/releases/tag/v4.9.0

Please check it out and report back if the issue persists with the recommended fixes. Thanks

tusharpandey13 avatar Aug 01 '25 13:08 tusharpandey13

@tusharpandey13 I am still running into this issue. It seems to only exist if I call auth0.getSession inside of my middleware. Here is an example of my middleware:

import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { auth0 } from '@/lib/auth0';

/**
 * Middleware to handle LTI authentication and protect routes with Auth0.
 *
 * @param request - The incoming request.
 * @returns The response from the middleware.
 */
export async function middleware(request: NextRequest) {
  // Let Auth0 handle its own routes first
  if (request.nextUrl.pathname.startsWith('/auth')) {
    return auth0.middleware(request);
  }

  // Allow access to public routes without authentication
  if (request.nextUrl.pathname === '/' || request.nextUrl.pathname === '/api/health') {
    return auth0.middleware(request);
  }

  // For all other protected routes, check authentication
  const session = await auth0.getSession(request);

  // If no session exists, redirect to login
  if (!session) {
    const { origin } = new URL(request.url);
    const returnTo = encodeURIComponent(request.nextUrl.pathname + request.nextUrl.search);
    return NextResponse.redirect(`${origin}/auth/login?returnTo=${returnTo}`);
  }

  // User is authenticated, let Auth0 middleware handle the response
  return auth0.middleware(request);
}

export const config = {
  matcher: '/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
};

chrisfernandes102 avatar Aug 06 '25 00:08 chrisfernandes102

I'm also seeing this with v4.9.0.

Notably, I was using the automatic login scheme, which threw warnings about falling back to the v2 logout because OIDC RP-initiated logouts aren't enabled on my tenant.

These may be entirely unrelated, but is anyone experiencing this issue using OIDC instead of the legacy v2 logout?

cpboyd avatar Aug 07 '25 23:08 cpboyd

I'm not calling auth0.getSession but I did implement async onCallback(error, context, session) {} which includes the session as argument (but again, is not referenced within my code).

cpboyd avatar Aug 11 '25 18:08 cpboyd

I'm also seeing this with v4.9.0.

These may be entirely unrelated, but is anyone experiencing this issue using OIDC instead of the legacy v2 logout?

Yes I am also seeing this and with OIDC.

UPDATE: after some more investigating, i believe my issue to be unrelated to this issue.

getinnocuous avatar Aug 13 '25 13:08 getinnocuous

I am seeing the IODC v2 logout as well.

marc-wilson avatar Aug 13 '25 16:08 marc-wilson

Hi @marc-wilson @getinnocuous @cpboyd @chrisfernandes102 👋, thanks for your inputs.

We'd like to gather more information from users experiencing this issue to help identify potential root causes.

If you're still experiencing transaction cookie stacking after v4.9.0+, please provide:

  • Browser Details:

    • Browser name and version
    • Third-party cookie settings (Chrome: Settings → Privacy → Cookies, Safari: Prevent cross-site tracking)
    • Any privacy extensions installed
  • Logout Strategy:

    • Are you using OIDC logout (logoutStrategy: "oidc") or v2 logout (logoutStrategy: "v2")?
    • Do you see the issue with both strategies or only one?
  • Network Environment:

    • Corporate network/proxy that might block third-party requests?
    • Any network-level cookie blocking?
  • Specific Scenario:

    • Does the cookie stacking happen during normal logout attempts?
    • Or during interrupted login flows (closing browser tabs, network issues)?

Background: We suspect that OIDC logout may be creating redirect loops when browsers block third-party cookies, leading to new transaction cookies being created without proper cleanup. This could explain why some users see continued issues even with improved cookie cleanup logic.

tusharpandey13 avatar Aug 13 '25 18:08 tusharpandey13

In my workflow, I was using auto which reported downgrading to a logout strategy of v2 since OIDC logout was disabled on the tenant.

I believe that both Chrome-based browsers and Firefox were affected. We downgraded to v3 of the library.

cpboyd avatar Aug 16 '25 00:08 cpboyd

@tusharpandey13, we're still experiencing this issue with 4.9.0. Here are our/my details, for as far as we could give them.

  • Browser details:

    • I'm using Firefox 141.0.3 on MacOS 15.5 (24F74) (M2).
    • I'm using 'Standard' privacy: Image That's a default setting, so i.m.o. not something that should break Auth0 functionality. I do not have any browser extensions installed, other than 1Password.
  • Logout strategy:

    • We've not set up a specific logoutStrategy, so it defaults to OIDC. However, when testing, it occured on both logoutStrategy logoutStrategy: 'oidc' and on logoutStrategy: 'v2'.
  • Network environment:

    • We do not have a corporate network or proxy that blocks third-party requests. The error occurs for users outside our network as well.
    • No network-level cookie blocking.
  • Scenario:

    • When visiting or redirecting (does not really matter how we visit) the /auth/login 'endpoint' we see 2 __txn cookies being set. Whenever we go back, only 1 is removed. When (silently) loggin in again, we see 2 cookies being set, 1 of which is deleted right after. (Part of) our middleware looks like this without the famous '__txn' removal lines:

      const { pathname } = request.nextUrl
      
      if (!pathname.startsWith('/auth')) {
        return next(request, event, response)
      }
      
      return auth0.middleware(request)
      

      then we'll see the cookies slowly stacking up. With this middleware:

      const { pathname } = request.nextUrl
      
      if (!pathname.startsWith('/auth')) {
        return next(request, event, response)
      }
      
      const authResponse = await auth0.middleware(request)
      
      // This is a workaround for this issue: https://github.com/auth0/nextjs-auth0/issues/1917
      // The auth0 middleware sets some transaction cookies that are not deleted after the login flow completes.
      // This causes stale cookies to be used in subsequent requests and eventually causes the request header to be rejected because it is too large.
      const reqCookieNames = request.cookies.getAll().map((cookie) => cookie.name)
      reqCookieNames.forEach((cookie) => {
        if (cookie.startsWith('__txn')) {
          authResponse.cookies.delete(cookie)
        }
      })
      return authResponse
      

      The __txn cookies are removed.

Example of our scenario, without the workaround (after a few login attempts to show stacking of cookies):

  • Before login: Image

  • When clicking/visiting login url: Image

  • After login: Image

When then enabling workaround:

  • Before login: Image

  • When clicking/visiting login url: Image

  • After login: Image

Please feel free to respond on this comment and contact me, would be glad to be of any assistance in resolving this issue.

We do struggle with a lot of randomly occuring The state parameter is invalid. errors after (silent) login-ins, which we haven't been able to resolve for months. Could that be related?

jesseflikweerteo avatar Aug 20 '25 12:08 jesseflikweerteo

Background: We suspect that OIDC logout may be creating redirect loops when browsers block third-party cookies, leading to new transaction cookies being created without proper cleanup. This could explain why some users see continued issues even with improved cookie cleanup logic.

@tusharpandey13 any luck on this update?

chrisfernandes102 avatar Aug 25 '25 23:08 chrisfernandes102