createCookieSessionStorage session expiration with conflicting maxAge and expires
0 reputation · 06 Apr 2024, 04:28 UTC
0 reputation · 06 Apr 2024, 04:28 UTC
When configuring session storage in Remix using createCookieSessionStorage, the API allows for the definition of both maxAge (defined in seconds) and expires (defined as a Date object) to control cookie lifetime.
There is uncertainty regarding the precedence logic when both properties are provided simultaneously. Specifically, it is unclear if the framework prioritizes the relative duration of maxAge or the absolute timestamp of expires when generating the Set-Cookie header.
This ambiguity complicates the implementation of precise session timeouts, as browser-level interpretation of these overlapping attributes can vary.
maxAge and expires are defined?When both maxAge and expires are provided to createCookieSessionStorage, maxAge takes precedence and expires is silently dropped. The resulting Set-Cookie header contains only a Max-Age attribute; no Expires attribute is emitted. There is no combined calculation — one attribute simply overrides the other.
Remix does not serialize cookies itself. createCookieSessionStorage passes your cookie options object through to the cookie npm package's serialize() function. That library's behavior is: if maxAge is present, it writes Max-Age and skips Expires entirely.
This is a deliberate choice, not an accident. RFC 6265 specifies that when a browser receives both Max-Age and Expires, Max-Age wins. Since Max-Age is also immune to client clock skew (a wrong system clock can make an absolute Expires date expire immediately or never), the library emits only the more reliable directive.
maxAge set: header gets Max-Age=<seconds>. This is the recommended approach for session timeouts.expires set: header gets Expires=<HTTP-date>. Works, but is vulnerable to client clock skew.Max-Age is sent. Your expires value has no effect — do not treat it as a fallback.Note that these options only control the cookie's lifetime in the browser. They do not enforce anything server-side — with cookie-based storage the session data lives in the cookie itself, so cookie expiry is the session expiry. If you later switch to a server-backed session store, you must enforce TTL separately.
Set up conflicting values and inspect the actual header:
const sessionStorage = createCookieSessionStorage({
cookie: {
name: "__session",
secrets: ["s3cret"],
maxAge: 60,
expires: new Date("2099-01-01"), // will be ignored
},
});
Commit a session in a loader/action and check the response:
curl -sv http://localhost:3000/your-route 2>&1 | grep -i set-cookie
You should see Max-Age=60 and no Expires attribute, confirming the precedence. You can also confirm the delegation by reading the serialize() source in the cookie package inside your node_modules.
Expires. If you genuinely must support them, set only expires and omit maxAge.Date.now() + maxAge * 1000) rather than reading the cookie header.cookie package, but if you pin unusual dependency versions, run the curl check above to confirm.Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.