Repository: auth0/nextjs-auth0 (ts)

Issue:
Clearcookie Doesn't Respect AUTH0_COOKIE_SAME_SITE
### Describe the problem
It seems that the `clearCookie` function used to remove the `appSession` cookie doesn't respect the `AUTH0_COOKIE_SAME_SITE` environment variable.

This means that if you set `AUTH0_COOKIE_SAME_SITE=none` to support cross-origin requests, users can log in, but when they try to logout, the `appSession` cookie isn't cleared.

### What was the expected behavior?

The `clearCookie` function should respect `AUTH0_COOKIE_SAME_SITE` when being used to clear `appSession`.

### Environment

- **Version of this library used:** 1.6.1


Pull request description:
Cookies with samesite=none must have the secure attr set
### Description

Fix issue where transient and session cookies weren't cleared when `SameSite=None`

> If `SameSite=None` is set, the cookie Secure attribute must also be set (or the cookie will be blocked)
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#none

### References

fixes #569 

### Testing

- [ ] This change adds test coverage for new/changed/fixed functionality

### Checklist

- [ ] I have added documentation for new/changed functionality in this PR or in auth0.com/docs
- [ ] All active GitHub checks for tests, formatting, and security are passing
- [ ] The correct base branch is being used, if not `main`


Patch:
diff --git a/src/auth0-session/cookie-store.ts b/src/auth0-session/cookie-store.ts
index ad6aca37..b09cd801 100644
--- a/src/auth0-session/cookie-store.ts
+++ b/src/auth0-session/cookie-store.ts
@@ -166,10 +166,7 @@ export default class CookieStore {
       debug('clearing all matching session cookies');
       for (const cookieName of Object.keys(cookies)) {
         if (cookieName.match(`^${sessionName}(?:\\.\\d)?$`)) {
-          clearCookie(res, cookieName, {
-            domain: cookieConfig.domain,
-            path: cookieConfig.path
-          });
+          clearCookie(res, cookieName, cookieConfig);
         }
       }
       return;
@@ -198,19 +195,13 @@ export default class CookieStore {
         setCookie(res, chunkCookieName, chunkValue, cookieOptions);
       }
       if (sessionName in cookies) {
-        clearCookie(res, sessionName, {
-          domain: cookieConfig.domain,
-          path: cookieConfig.path
-        });
+        clearCookie(res, sessionName, cookieConfig);
       }
     } else {
       setCookie(res, sessionName, value, cookieOptions);
       for (const cookieName of Object.keys(cookies)) {
         if (cookieName.match(`^${sessionName}\\.\\d$`)) {
-          clearCookie(res, cookieName, {
-            domain: cookieConfig.domain,
-            path: cookieConfig.path
-          });
+          clearCookie(res, cookieName, cookieConfig);
         }
       }
     }
diff --git a/src/auth0-session/transient-store.ts b/src/auth0-session/transient-store.ts
index 500ce466..245f67ea 100644
--- a/src/auth0-session/transient-store.ts
+++ b/src/auth0-session/transient-store.ts
@@ -133,10 +133,10 @@ export default class TransientStore {
    */
   read(key: string, req: IncomingMessage, res: ServerResponse): string | undefined {
     const cookie = getCookie(req, key);
-    const { domain, path } = this.config.session.cookie;
+    const cookieConfig = this.config.session.cookie;
 
     let value = getCookieValue(key, cookie, this.keyStore);
-    clearCookie(res, key, { domain, path });
+    clearCookie(res, key, cookieConfig);
 
     if (this.config.legacySameSiteCookie) {
       const fallbackKey = `_${key}`;
@@ -144,7 +144,7 @@ export default class TransientStore {
         const fallbackCookie = getCookie(req, fallbackKey);
         value = getCookieValue(fallbackKey, fallbackCookie, this.keyStore);
       }
-      clearCookie(res, fallbackKey, { domain, path });
+      clearCookie(res, fallbackKey, cookieConfig);
     }
 
     return value;
diff --git a/src/auth0-session/utils/cookies.ts b/src/auth0-session/utils/cookies.ts
index 932f8e1a..ea5e689b 100644
--- a/src/auth0-session/utils/cookies.ts
+++ b/src/auth0-session/utils/cookies.ts
@@ -22,5 +22,18 @@ export const set = (res: ServerResponse, name: string, value: string, options: C
 };
 
 export const clear = (res: ServerResponse, name: string, options: CookieSerializeOptions = {}): void => {
-  set(res, name, '', { ...options, maxAge: 0 });
+  const { domain, path, secure, sameSite } = options;
+  const clearOptions: CookieSerializeOptions = {
+    domain,
+    path,
+    maxAge: 0
+  };
+  // If SameSite=None is set, the cookie Secure attribute must also be set (or the cookie will be blocked)
+  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#none
+  if (sameSite === 'none') {
+    clearOptions.secure = secure;
+    clearOptions.sameSite = sameSite;
+  }
+
+  set(res, name, '', clearOptions);
 };


Test patch:
diff --git a/tests/auth0-session/handlers/logout.test.ts b/tests/auth0-session/handlers/logout.test.ts
index 858b77e8..74fafc45 100644
--- a/tests/auth0-session/handlers/logout.test.ts
+++ b/tests/auth0-session/handlers/logout.test.ts
@@ -157,4 +157,27 @@ describe('logout route', () => {
     expect(res.statusCode).toEqual(302);
     expect(res.headers.location).toEqual(returnTo);
   });
+
+  it('should clear session cookie when SameSite=None', async () => {
+    const baseURL = await setup(
+      { ...defaultConfig, idpLogout: false, session: { cookie: { sameSite: 'none' } } },
+      { https: true }
+    );
+    const cookieJar = await login(baseURL);
+    cookieJar.setCookieSync('foo=bar', baseURL);
+
+    await get(baseURL, '/session', { cookieJar });
+    expect(fromCookieJar(cookieJar, baseURL)).toMatchObject({
+      appSession: expect.any(String),
+      foo: 'bar'
+    });
+
+    const { res } = await get(baseURL, '/logout', { cookieJar, fullResponse: true });
+    const cookies = fromCookieJar(cookieJar, baseURL);
+    const sessionCookie = res.headers['set-cookie'].find((s: string) => /^appSession/.test(s));
+    expect(sessionCookie).toMatch(/Secure/);
+    expect(sessionCookie).toMatch(/SameSite=None/);
+    expect(cookies).toHaveProperty('foo');
+    expect(cookies).not.toHaveProperty('appSession');
+  });
 });