Skip to content

Session cookie silently dropped when response headers are flushed before res.end() #835

Description

@kosanna

Problem

In v3, session cookie persistence moved from an on-headers hook (which fires at res.writeHead() time) to a res.end() wrapper. This means any response that flushes headers before res.end() — via res.flushHeaders(), res.write(), res.writeHead(), res.sendFile(), or res.download() — will have res.headersSent === true by the time the cookie write runs, and the session cookie is silently dropped.

This affects any application using streaming/chunked responses, which is a common pattern for server-side rendered applications that flush the <head> early to allow the browser to start loading assets while the body renders.

There is no error, no warning, and no workaround. The session just stops being persisted.

Expected behavior

The session cookie should be written regardless of whether the response is streamed or buffered. The v2 behavior (using on-headers) handled this correctly.

Reproduction

app.use(auth(config));

app.get('/', (req, res) => {
  res.flushHeaders();       // headers sent
  res.write('<html>...');   // streaming body
  // ... later ...
  res.end('</html>');       // session cookie write runs here, but headersSent is true — cookie lost
});

Suggested fix

Expose a mechanism to write the session cookie before the response starts streaming. Either:

  1. Restore on-headers as the default — this was the v2 behavior, it fires at writeHead() time (the last moment headers are mutable), and it works with both buffered and streamed responses.

  2. Expose a public API like req.oidc.persistSession(res) that consumers can call explicitly when they know headers are about to flush. This would allow streaming applications to call it at the right moment without relying on internal timing.

Option 1 is backward-compatible and covers all cases transparently. Option 2 gives consumers control but requires awareness of the issue.

Environment

  • express-openid-connect: v3 (breaking change from v2)
  • Node.js: >=22
  • Express: 4.x
  • Use case: streaming SSR with res.flushHeaders() before res.end()

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions