Repository: auth0/nextjs-auth0 (ts)

Issue:
Refreshing token in API route - subsequent request returns 401 and clears session
### Checklist

- [ ] The issue can be reproduced in the [nextjs-auth0 sample app](https://github.com/auth0-samples/auth0-nextjs-samples/tree/main/Sample-01) (or N/A).
- [X] I have looked into the [Readme](https://github.com/auth0/nextjs-auth0#readme), [Examples](https://github.com/auth0/nextjs-auth0/blob/main/EXAMPLES.md), and [FAQ](https://github.com/auth0/nextjs-auth0/blob/main/FAQ.md) and have not found a suitable solution or answer.
- [X] I have looked into the [API documentation](https://auth0.github.io/nextjs-auth0/) and have not found a suitable solution or answer.
- [X] I have searched the [issues](https://github.com/auth0/nextjs-auth0/issues) and have not found a suitable solution or answer.
- [X] I have searched the [Auth0 Community](https://community.auth0.com) forums and have not found a suitable solution or answer.
- [X] I agree to the terms within the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md).

### Description

I have a NextJS app with multiple /api routes that communicate to external system. I'm trying to get token refresh to work and so far isolated the following problem: after successful refresh, the session is updated, new session cookies are sent to the client, but the very next request to the same API route fails with 401 and cookies are cleared:

![image](https://github.com/auth0/nextjs-auth0/assets/1315023/187355b5-6736-46be-8e88-148697003366)


### Reproduction

1. Be logged in to the app
2. Open /api/test route in a separate browser tab (route is wrapped into withApiAuthRequired and uses getAccessToken inside of it)
3. Wait / or force token refresh and reload the tab
4. Token is successfully refreshed and new session cookies are sent in response, all good so far
5. Reload the tab again
6. 401 error is returned and the session is cleared

Is there anything special I'm missing about API routes?

### Additional context

_No response_

### nextjs-auth0 version

1.9.3

### Next.js version

12.1

### Node.js version

16.20.0

Pull request description:
Clean up erroneous cookies when chunk size decreases
### 📋 Changes

When a session is updated it might decrease in size. If the session is large and split into several cookies, the number of cookies could reduce (e.g. from 3 to 2), in these cases the 3rd cookie needs to be deleted

### 📎 References

fixes #1293 

(this code is taken from the Beta)

Patch:
diff --git a/src/auth0-session/session/stateless-session.ts b/src/auth0-session/session/stateless-session.ts
index 0d3d9d40..40e73791 100644
--- a/src/auth0-session/session/stateless-session.ts
+++ b/src/auth0-session/session/stateless-session.ts
@@ -122,24 +122,27 @@ export class StatelessSession<
     const value = await this.encrypt(session, { iat, uat, exp });
 
     const chunkCount = Math.ceil(value.length / this.chunkSize);
+
+    const existingCookies = new Set(
+      Object.keys(cookies).filter((cookie) => cookie.match(`^${sessionName}(?:\\.\\d)?$`))
+    );
+
     if (chunkCount > 1) {
       debug('cookie size greater than %d, chunking', this.chunkSize);
       for (let i = 0; i < chunkCount; i++) {
         const chunkValue = value.slice(i * this.chunkSize, (i + 1) * this.chunkSize);
         const chunkCookieName = `${sessionName}.${i}`;
         cookieSetter.set(chunkCookieName, chunkValue, cookieOptions);
-      }
-      if (sessionName in cookies) {
-        cookieSetter.clear(sessionName, cookieOptions);
+        existingCookies.delete(chunkCookieName);
       }
     } else {
       cookieSetter.set(sessionName, value, cookieOptions);
-      for (const cookieName of Object.keys(cookies)) {
-        if (cookieName.match(`^${sessionName}\\.\\d$`)) {
-          cookieSetter.clear(cookieName, cookieOptions);
-        }
-      }
+      existingCookies.delete(sessionName);
     }
+
+    // When the number of chunks changes due to the cookie size changing,
+    // you need to delete any obsolete cookies.
+    existingCookies.forEach((cookie) => cookieSetter.clear(cookie, cookieOptions));
     cookieSetter.commit(res, this.config.session.name);
   }
 


Test patch:
diff --git a/tests/auth0-session/session/stateless-session.test.ts b/tests/auth0-session/session/stateless-session.test.ts
index 3876ad59..8ffb2c7a 100644
--- a/tests/auth0-session/session/stateless-session.test.ts
+++ b/tests/auth0-session/session/stateless-session.test.ts
@@ -182,6 +182,29 @@ describe('StatelessSession', () => {
     expect(cookies).not.toHaveProperty(['appSession.0']);
   });
 
+  it('should clean up chunked cookies when chunk size reduces', async () => {
+    const baseURL = await setup(defaultConfig);
+    const appSession = await encrypted({
+      big_claim: randomBytes(2000).toString('base64')
+    });
+    expect(appSession.length).toBeGreaterThan(4000);
+    const cookieJar = toCookieJar(
+      {
+        'appSession.0': appSession.slice(0, 100),
+        'appSession.1': appSession.slice(100, 200),
+        'appSession.2': appSession.slice(200)
+      },
+      baseURL
+    );
+    const { data: session, res } = await get(baseURL, '/session', { cookieJar, fullResponse: true });
+    expect(res.headers['set-cookie']).toHaveLength(3);
+    expect(session.claims).toHaveProperty('big_claim');
+    const cookies = fromCookieJar(cookieJar, baseURL);
+    expect(cookies).toHaveProperty(['appSession.0']);
+    expect(cookies).toHaveProperty(['appSession.1']);
+    expect(cookies).not.toHaveProperty(['appSession.2']);
+  });
+
   it('should set the default cookie options on http', async () => {
     const baseURL = await setup(defaultConfig);
     const appSession = await encrypted();