Simplifies the handling of HTTP cookies.
- Create string or JSON cookies with an optional signature.
- Parse the Cookie header into a key-value Object.
- Use the cookie parser as a middleware in Express.
Note
This is a lightweight alternative library to cookie-parser with no dependencies.
npm i flipeador/node-http-cookies#semver:^2.0.0
Cookies are mainly used for storing credentials, user preferences, site settings, and tracking data.
- The cookie name can contain any US-ASCII characters except for control characters, space, tab, and separators (
( ) < > @ , ; : \ " / [ ] ? = { }
). - The cookie value can optionally be wrapped in double quotes and include any US-ASCII character excluding control characters, whitespace, double quotes, commas, semicolons, and backslashes.
- The cookie name or value can be URI encoded to satisfy the allowed character requirement.
- The cookie value can be a number, or a JSON-stringified object prefixed with
j:
. - The cookie value can be signed to ensure that it has not been tampered with, and prefixed with
s:
.
The server sends cookies to the browser by setting the Set-Cookie header:
[
'<cookie-name>=<cookie-value>; attribute1; attribute2; ...',
...
]
The browser sends cookies back to the server by setting the Cookie header:
'<cookie-name>=<cookie-value>; ...'
Cookie names can have specific semantics via the following prefixes:
Prefix | Description |
---|---|
__Secure- |
Require the cookie to be set from a secure page (HTTPS). |
__Host- |
Make the cookie bound to the hostname (and not the registrable domain). |
The following are the cookie attributes recognized by the library:
Attribute | Default | Description | Note |
---|---|---|---|
Domain |
— | Defines the host to which the cookie will be sent. | — |
Path |
/ |
Restrict the cookie to a specific path, including subpaths. | — |
Secure |
false |
Set the cookie over HTTPS only, except on localhost. | — |
HttpOnly |
false |
Make the cookie inaccessible to the JavaScript Document.cookie API. |
— |
SameSite |
lax |
Origin relationship between request and resource. | Values: strict lax none . |
Partitioned |
false |
The cookie should be stored using partitioned storage. | Requires the Secure attribute. |
Expires |
— | The exact expiration date in the RFC 7231 format. | new Date().toUTCString() |
Max-Age |
— | Number of seconds until the cookie expires. | Invalidates Expires . |
Note
- The value
true
will cause the attribute to be added to the cookie with no value assigned. - The values
false
NaN
''
will cause the attribute not to be added to the cookie. - The values
null
undefined
will cause the attribute to keep the default value, if any.
The following are special attributes used as options:
Option | Description |
---|---|
secret |
Secret to use to sign the cookie value. |
By specifying the Expires
or Max-Age
attribute, you can create a persistent cookie.
If the server does not provide information about the expiration of the cookie by specifying the exact date or after a specific length of time, it becomes a session cookie and is removed when the browser shuts down.
A cookie can be removed by specifying a zero or negative number in the Max-Age
attribute, wich will expire the cookie immediately.
Cookies Having Independent Partitioned State (CHIPS) allows developers to opt a cookie into partitioned storage, with separate cookie jars per top-level site, which restricts the contexts in which a cookie is available to only those whose top-level document is same-site with the top-level document that initiated the request that created the cookie. Partitioned cookies allows to embedded sites which are cross-site with the top-level frame to have access to HTTP state which cannot be used for tracking across multiple top-level sites.
- Visit chrome://flags/#test-third-party-cookie-phaseout and enable partitioned cookies.
- The Application tab shows both partition and unparitioned stored cookies, even if they are not accessible in the current page.
- Unpartitioned third-party cookies will not be available to embedded sites on different top-level sites.
- You can better visualize unpartitioned cookies that are being blocked by checking
Only show cookies with an issue
in the DevTools.
- https://developer.mozilla.org/docs/Web/Privacy/Partitioned_cookies
- https://developers.google.com/privacy-sandbox/3pcd/chips
- https://www.ietf.org/archive/id/draft-cutler-httpbis-partitioned-cookies-01.html
- https://github.com/privacycg/CHIPS
The SameSite
attribute allows to declare if the cookie should be restricted to a same-site or cross-site context, providing some protection against Cross-site Request Forgery (CSRF) attacks.
If the server stores a session token with full access to the user's account in the cookies, you don't want the browser to send it in a malicious request from an unauthorized site.
The included cookie is first-party when the request is made in a same-site context, and third-party when the request is made in a cross-site context.
SameSite | Description |
---|---|
Strict |
Cookies will only be sent in a same-site context. |
Lax |
Like Strict , except the browser also sends the cookie when the user navigates to the cookie's origin site (e.g. by following a link from an external/different site). |
None |
Cookies will be sent in both cross-site and same-site contexts. Requires the Secure attribute. |
Important
If the SameSite
attribute is set to None
, browsers that have enabled the third-party cookie phaseout will require the Partitioned
attribute to be present, otherwise unparitioned cookies will be blocked in third-party contexts.
Top-level domains (TLD) are listed in the Root Zone Database. The Public Suffix List (PSL) is a catalog of certain Internet domain names, whose entries in the list are also referred to as effective Top-Level Domains (eTLD). The PSL is used for some domains where the TLD is not granular enough to identify the site.
User agents group URIs together into protection domains called origins. Two URIs are part of the same origin if they have the same scheme, host, and port (RFC 6454). All cross-site requests are necessarily cross-origin, but not all cross-origin requests are cross-site.
The same-site context treats all subdomains of the eTLD+1
to be equivalent to the root domain.
Origin | eTLD+1 | Same-Site | Cross-Site |
---|---|---|---|
http://www.example.com |
.example.com |
http://example.com http://x.example.com http://x.y.example.com |
https://www.example.com example.net username.github.io |
username.github.io |
username.github.io |
x.username.github.io x.y.username.github.io |
otheruser.github.io |
Important
- If you are dealing with third-party cookies (e.g. for tracking purposes), make sure to specify the Partitioned attribute.
- The definition of site extends beyond the host and require scheme matches as well, although some browsers may not yet be URL scheme-aware, see Browser Compatibility.
Note
The Sec-Fetch-Site header can be used to determine if the request is same-origin, same-site, cross-site, or it is a user-originated operation (e.g. entering a URL into the address bar, opening a bookmark, or a drag-and-drop operation).
- https://github.com/flipeador/node-http-cors
- https://jub0bs.com/posts/2021-01-29-great-samesite-confusion
- https://jub0bs.com/posts/2022-08-04-scraping-the-bottom-of-the-cors-barrel-part1
import express from 'express';
import {
cookieParser,
parseJsonCookie,
parseSignedCookie
} from '@flipeador/node-http-cookies';
const app = express();
// Add the middleware here and not individually to the routes.
// This does not have a significant impact on performance,
// as the cookies are not parsed immediately for every request.
// Cookies are parsed only once on first access to the 'req.cookies' property.
// The middleware also sets a function 'res.cookie()' that allows cookies to be added.
app.use(cookieParser());
app.get('/set-cookie', (req, res) => {
res.cookie('string', 'value', {
secure: true,
sameSite: 'none',
partitioned: true,
// Restrict the cookie to the current path and subpaths.
path: `${req.baseUrl}${req.path}`
});
res.cookie('json', ['value']);
res.cookie('signed', 'value', { secret: 'XXX' });
res.json(req.cookies);
});
app.get('/get-cookie', (req, res) => {
res.json({
string: req.cookies.string ??
'not accessible due to different path',
json: parseJsonCookie(req.cookies.json),
signed: parseSignedCookie(req.cookies.signed, 'XXX')
});
});
app.listen(3000, () => {
console.log('Server is running!');
console.log('http://localhost:3000/set-cookie');
console.log('http://localhost:3000/get-cookie');
});
This project is licensed under the Apache License 2.0. See the license file for details.