tl;dr
When a cookie includes an
Expires
attribute or an HTTP response includes caching headers likeExpires
orCache-Control
, their validity depends on the server'sDate
header if present. Otherwise, the browser uses its local time. This can lead to issues in tests with mocked time or inconsistent cache behavior.
Cookie Expires
depends on the Date
header or browser time
When a cookie includes an Expires
attribute, the browser evaluates the expiration date relative to a reference time:
- If the HTTP response includes a
Date
header, the browser uses this time as the reference. - If no
Date
header is present, the browser falls back to its own local system time.
This subtle behavior ensures that cookies remain consistent with server time, even if the client’s clock is incorrect. However, this can lead to unexpected issues in specific scenarios.
Why does this matter?
Mocked time in tests
Consider a Rails application where you use the remember_me
feature of Clearance
for authentication. The cookie for this feature might include an Expires
attribute, specifying its validity:
Set-Cookie: remember_me=abc123; Expires=Fri, 15 Dec 2023 12:00:00 GMT
When running tests, you might mock the time (e.g., using Timecop
) to simulate past or future dates. If the HTTP response also includes a Date
header, the browser uses this as the reference for the Expires
calculation:
Date: Fri, 08 Dec 2023 12:00:00 GMT
In this case, the cookie is evaluated as valid until Fri, 15 Dec 2023 12:00:00 GMT
, regardless of the browser's local time.
However, if the Date
header is missing, the browser relies on its own local clock. If your mocked time is in the past relative to the expiration, the cookie might incorrectly appear as expired.
Relevance for Caching
This behavior is also relevant for HTTP caching. Headers like Expires
or Cache-Control: max-age
use the Date
header as a reference time. If the Date
header is missing, the browser relies on its local time, which can lead to inconsistent cache behavior. Ensure your responses include a Date
header to avoid issues with cache expiration.
How to handle this in tests?
Ensure your server sets a consistent Date
header, especially in test environments. If your test mocks time and you observe premature cookie expiration, consider adding middleware to enforce a Date header:
class AddDateHeaderMiddleware
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
if headers['Date'].blank?
headers['Date'] = Time.now.httpdate
end
[status, headers, body]
end
end