Skip to content

During logout, both a blank and non-blank __Secure-authjs.session-token Set-Cookie header are sent by next-auth #12909

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
NickCrews opened this issue Apr 25, 2025 · 5 comments
Labels
bug Something isn't working triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime.

Comments

@NickCrews
Copy link

Environment

(using https://github.com/nextauthjs/next-auth-example running in a github codespace)

  System:
    OS: Linux 6.8 Ubuntu 20.04.6 LTS (Focal Fossa)
    CPU: (2) x64 AMD EPYC 7763 64-Core Processor
    Memory: 5.96 GB / 7.75 GB
    Container: Yes
    Shell: 5.0.17 - /bin/bash
  Binaries:
    Node: 20.19.0 - ~/nvm/current/bin/node
    Yarn: 1.22.22 - /usr/bin/yarn
    npm: 10.8.2 - ~/nvm/current/bin/npm
    pnpm: 10.6.4 - ~/nvm/current/bin/pnpm
  npmPackages:
    @auth/unstorage-adapter: ^2.0.0 => 2.9.0 
    next: latest => 15.3.1 
    next-auth: beta => 5.0.0-beta.27 
    react: ^18.2.0 => 18.3.1 

Reproduction URL

https://github.com/NickCrews/next-auth-example

Describe the issue

During the logout process, next-auth sends a Set-Cookie header to the client to inform the browser to clear the __Secure-authjs.session-token cookie. Unfortunately, it ALSO sends a second Set-Cookie header that can revalidate the cookie, depending on the order they are received. See the screenshot below (repro steps below), you can see the response includes two Set-Cookie headers:

  • __Secure-authjs.session-token=<encrypted token>; Path=/; Expires=Sun, 25 May 2025 22:22:36 GMT; HttpOnly; Secure; SameSite=Lax
  • __Secure-authjs.session-token=; Path=/; Max-Age=0; Secure; HttpOnly; SameSite=lax

Image

The problem is that this is undefined behavior, according to https://www.rfc-editor.org/rfc/rfc6265#section-4.1.1, in particular "Servers SHOULD NOT include more than one Set-Cookie header field in the same response with the same cookie-name."

For some server runtimes, such as vercel and node, the blank cookie appears second, while on others, such as cloudflare workers and netlify, the blank cookie appears first. The outcome is that, at least on Chrome, the browser chooses to use the LAST header. So, if you use next-auth on cloudflare or netlify, the non-blank cookie is second, and thus the user is never signed out.

I found this question on a netlify help forum and this pointed me in the right direction.
answers.netlify.com/t/next-auth-session-not-clearing/119104/13
In that forum, they initially phrased it as a problem with the netlify runtime. But I think it actually is a problem at our level: we shouldn't be setting two cookies at all.

How to reproduce

  1. go to next-auth-example.vercel.app
  2. login using any method
  3. open devtools, go to the network tab, and clear the network log
  4. hit the logout button
  5. look at the headers returned in the response

Expected behavior

The solution I would like to see is that next-auth only sends the blank cookie on logout. I wonder if the non-blank cookie is coming from middleware that is automatically refreshing the cookie on every request?

IDK if you already have a relationship with the next-on-cloudflare team, but they may be interested in this.

@NickCrews NickCrews added bug Something isn't working triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime. labels Apr 25, 2025
@NickCrews
Copy link
Author

On my prod app, I was able to work around this by manually overriding the session set-cookie headers in the middleware, after authjs's middleware runs. I haven't tested this in the official example app.

import { NextRequest } from "next/server";
import {auth} from "~/auth";

export async function middleware(req: NextRequest) {
  // @ts-expect-error the type that auth returns is NOT a NextResponse but a plain Response
  const resp: Response = await auth(req);
  
  if (req.nextUrl.pathname.startsWith("/api/auth/signout")) {
    console.log("middleware signout", resp.headers.getSetCookie());
    // remove all session cookies, then re-add one explicitly
    const nonSessionCookies = resp.headers.getSetCookie().filter((cookie) => {
      return ! cookie.startsWith("authjs.session-token");
    });
    resp.headers.delete("Set-Cookie");
    for (const cookie of nonSessionCookies) {
      resp.headers.append("Set-Cookie", cookie);
    }
    resp.headers.set("Set-Cookie", "authjs.session-token=; Path=/; Max-Age=0; Secure; HttpOnly; SameSite=lax");
  }
  
  console.log("middleware", resp.headers.getSetCookie());
  return resp;
}

export const config = {
  matcher: [
    // Skip Next.js internals and all static files, unless found in search params
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
    // Always run for API routes
    '/(api|trpc)(.*)',
  ],
};

@NickCrews
Copy link
Author

cc @dnpg, who was the original filer in the netlify issue I linked above.

@ThangHuuVu
Copy link
Member

I just did a quick test on the example app, and saw that by disabling the middleware, this issue is gone.
I also saw that the signout action correctly returns a single, empty value Set-Cookie:authjs.session-token=; Path=/; Max-Age=0; HttpOnly; SameSite=lax header.

@weisisheng
Copy link

How can I access the value returned by the new cookie setting? My session (with user data) remains intact even after signout() completes and I try to revalidate path in next.js 15. Thanks in advance.

@NickCrews
Copy link
Author

@weisisheng I don't understand what you are asking, can you elaborate?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime.
Projects
None yet
Development

No branches or pull requests

3 participants