tough-cookie: an RFC 6265 cookie jar for Node, with the RFC 6265bis parts still in draft
RFC6265 Cookies and CookieJar for Node.js
At a glance
- What is it?
- tough-cookie is a Node implementation of RFC 6265 for parsing, storing and retrieving cookies, maintained by Salesforce and used underneath a lot of HTTP tooling. It suits a developer who needs a correct cookie jar rather than a header split, and its SameSite and cookie prefix support is explicitly a work in progress.
- Who is it for?
- tough-cookie fits a Node project that has to behave like a browser about cookies, whether that is a test harness replaying a session or a scraping tool maintaining state across requests, because the jar applies the domain, path, secure and expiry rules instead of treating the header as text.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.
Star history
- salesforce/tough-cookie1,067 ★ · +1
| Repository | 2026-10-21 | 2026-11-07 |
|---|---|---|
| salesforce/tough-cookie | 1.1k | 1.1k |
1 Stars in the last 17 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
A jar that applies the rules, not a header split
tough-cookie is a Node.js implementation of RFC6265 for cookie parsing, storage, and retrieval. The words parsing, storage and retrieval are the three operations, and the middle one is what separates it from the usual approach.
Most code that deals with cookies splits a header on semicolons and reads the name and the value. That works until you need to know which cookies a request to a specific URL should carry, which is when the rules start to matter. Domain matching, path matching, the secure attribute, expiry, the public suffix problem, all of it. A cookie jar applies those rules, and a jar that does not apply them is not a cookie implementation, it is a parser with a nicer syntax.
The API separates the two halves cleanly. A `Cookie` object is the value type, and a `CookieJar` is the container that decides which cookies apply. The quick start shows both:
const cookieJar = new CookieJar() // uses the in-memory store by default
await cookieJar.setCookie(resCookie, 'https://example.com/')
const matchingCookies = await cookieJar.getCookies('https://example.com/')The `Cookie` object is also usable on its own, and the example parses a `Cookie` request header by splitting on semicolons, mapping each piece through `Cookie.parse` and filtering out the undefined results, then joining the `cookieString()` values back together with a semicolon and a space. The filtering matters, because a malformed segment should be dropped rather than producing a broken header.
On the response side the same object handles `Set-Cookie`. Parsing `foo=bar; Domain=example.com; Path=/; Max-Age=3600` gives you a cookie you can store, and `toString()` turns it back into a header. That round trip, parse a header, hold the object, write it back, is the whole use case, and the type is what carries the attributes that a string would lose.
The package is BSD-3-Clause licensed, published as `tough-cookie` on npm, and the description in the manifest is RFC6265 Cookies and Cookie Jar for node.js. The keywords list includes RFC6265 and RFC2965, which is a hint about the scope of the older cookie spec it also covers.
SameSite is a retrieval context, not just an attribute
The RFC6265bis support is the part of the library with the most design in it, and the documentation is upfront that it is unfinished. Support for RFC6265bis is being developed, and as these revisions to RFC6265 are still in Active Internet-Draft state, the areas of support that follow are subject to change.
The SameSite feature exists to let servers and supporting clients mitigate certain types of CSRF attacks by disallowing SameSite cookies from being sent cross-origin.
What makes this interesting is that SameSite is not only a stored attribute. It is also a property of the request context, which means the jar has to know what kind of request it is. The example stores three cookies with different SameSite values and then retrieves with a context:
const laxCookies = await cookieJar.getCookies('http://example.com/index.html', {
// the first cookie (strict=authorized) will not be returned if the context is 'lax'
// but the other two cookies will be returned
sameSiteContext: 'lax',
})So a `SameSite=strict` cookie is withheld from a lax context while a `SameSite=lax` and an unmarked cookie are returned. That is the correct behaviour, and implementing it requires the jar to compare a stored attribute against a per-request context, which is a different shape of API from the older cookie handling most libraries expose.
The documentation recommends reading RFC6265bis Section 8.8 for more details on SameSite cookies, security considerations, and defense in depth, and it also points out the wider point. A SameSite cookie is one layer, and the specification text describes the layers as defense in depth rather than as a complete answer.
The practical warning is in the framing rather than in any explicit caveat. If you are using this to reproduce browser behaviour in a test harness, the behaviour you get is what the draft says today, and drafts change. A test that asserts a `SameSite=strict` cookie is withheld may need revisiting when the specification settles, and a client that depends on a particular context mapping should be written so the mapping is visible rather than buried.
Cookie prefixes let a server state its own requirements
The second RFC6265bis feature is cookie prefixes, and the rule is short enough to state exactly. A cookie prefix is a way to indicate that a given cookie was set with a set of attributes simply by inspecting the first few characters of the cookie's name. Two prefixes are defined.
`__Secure-` means that if a cookie's name begins with a case-sensitive match for the string, then the cookie was set with a Secure attribute. `__Host-` means that a case-sensitive match means the cookie was set with a Secure attribute, a Path attribute with a value of `/`, and no Domain attribute.
The purpose is to let the server express a requirement and the client enforce it. A server that sets a `__Host-` cookie has said that it must not be scoped to a domain, and a client that accepts the cookie without checking has thrown that away.
So the jar checks. If prefixSecurity is enabled for CookieJar, then cookies that match the prefixes defined above but do not obey the attribute restrictions are not added. And the option has three values, which is a good sign of care about the failure mode.
`silent` is the default: enable cookie prefix checking but silently fail to add the cookie if conditions are not met. `strict` enables cookie prefix checking and errors out if conditions are not met. And `unsafe-disabled` disables cookie prefix checking entirely.
The middle one is the interesting choice to offer, because it answers the question of who is responsible for the failure. With `silent`, a cookie set in the wrong way simply does not appear, and a developer chasing a missing cookie has no signal. With `strict`, the setCookie call throws, and the problem is at the point of the mistake. The default is the forgiving one, which is a reasonable choice for a library, and the strict one is there for the people who would rather fail loudly.
There is an escape hatch on the error behaviour too. If ignoreError is passed in as true when setting a cookie then the error is silent regardless of the prefixSecurity option, assuming it is enabled.
The example shows the mechanism working. With `prefixSecurity: 'silent'`, a `__Secure-SID` cookie set against an http URL is silently ignored because the URL is insecure, and the same cookie set against an https URL is stored.
localhost counts as secure, and you can turn that off
There is a third RFC6265bis behaviour in the library that surprises people, and the documentation handles it head on rather than leaving it to be discovered.
The definition of a Secure connection is not explicitly defined by RFC6265bis, and the specification text quoted in the documentation says that typically user agents consider a connection secure if the connection makes use of transport-layer security, such as SSL or TLS, or if the host is trusted. For example, most user agents consider https to be a scheme that denotes a secure protocol and localhost to be a trusted host.
And since most web browsers treat localhost as a trustworthy origin, by default, so does tough-cookie.
The consequence is concrete. If your test suite runs against `http://localhost`, a cookie marked `Secure` will be accepted, which is not what a browser would do if that host were not localhost. A test that asserts a Secure cookie is rejected therefore fails against localhost and passes against a real hostname, which is the kind of inconsistency that makes people distrust their own suite.
The fix is a jar option rather than a workaround:
const cookieJar = new CookieJar(new MemoryCookieStore(), {
// add configuration so localhost will not be considered trustworthy
// (fyi - this doesn't apply to https cookies on localhost as those use a secure protocol)
allowSecureOnLocal: false,
})With that set, a `Secure` cookie for localhost is persisted to storage on set, but on retrieval it will not be returned. The comment about https cookies on localhost not being affected is a useful precision, since an https cookie uses a secure protocol regardless of the host's trustworthiness.
The design here is worth reading as a pattern. The library follows the browser default, because that is what a library for reproducing browser behaviour should do, and then gives you a switch for the case where the default is wrong for your setup, which is the case where you are deliberately testing the difference. Making the deviation explicit and one option is much better than a library that quietly diverges from the browsers.
The store is pluggable, and the docs are generated
The last structural piece is the store, and it is where the library stops being a header parser.
A `CookieJar` is constructed with a store, and the default is the in-memory one. Every example that needs a specific store passes `new MemoryCookieStore()` explicitly, and that is the point: the store is a separate concern from the rules, so the rules can be tested without touching persistence, and persistence can be swapped without touching the rules. A test that asserts cookie behaviour should not depend on a filesystem, and a production jar should not have to reimplement the matching logic to change where the data lives.
The `getCookies` and `setCookie` calls are both asynchronous, which is a consequence of the store being an interface rather than a variable, since a store might be backed by a database or a remote cache. It also means the API is awkward to use synchronously, which is a fair trade for the flexibility.
The repository layout shows how carefully the package is maintained. There is an `api/` directory with an `api-documenter.json` and an `api-extractor.json` at the root, which are the configuration for the API documentation tooling, and a `validate-generated-files.sh` script that checks the generated output matches what is checked in. There is a `version-template.ejs` template, a `CODEOWNERS` file, a `SECURITY.md` and a `CHANGELOG.md`.
The API documentation the README points to is at `./api/docs/tough-cookie.md`, and the package is marked as private in the manifest, which is a detail worth noting. The library is published to npm and the repository is the source, but the manifest is marked private so the package is not accidentally published from a fork.
The Node version policy is also stated, and it is the kind of policy that matters for a dependency this widely used. The project follows the Node.js release schedule and supports all versions that are in Active LTS or Maintenance. They will always do a major release when dropping support for older versions of node, and they will do so in consultation with their community.
So the supported range moves with Node's own schedule, and the signal that a version is being dropped is a major release, which for a library like this is a well-understood event.
Editorial conclusion
tough-cookie fits a Node project that has to behave like a browser about cookies, whether that is a test harness replaying a session or a scraping tool maintaining state across requests, because the jar applies the domain, path, secure and expiry rules instead of treating the header as text. Skip it if you only need to set a header on one request, since that is one line of code, and read the RFC 6265bis sections before you rely on the SameSite and prefix behaviour, because the documentation states that support is being developed and the areas covered are subject to change. Before you upgrade across the 6.0 line, note the version history with 6.0.0 in August 2025, 6.0.1 in March 2026 and 6.0.2 in July 2026. The last push was on 2026-10-14.
Frequently asked questions
What is tough-cookie used for?
It is a Node.js implementation of RFC6265 for cookie parsing, storage, and retrieval, providing a Cookie type for individual cookies and a CookieJar that applies the domain, path, secure and expiry rules when deciding which cookies a request should carry.
How does tough-cookie handle SameSite cookies?
You pass a sameSiteContext to getCookies, and the jar withholds cookies whose SameSite attribute does not permit that context, so a strict cookie is not returned in a lax context while lax and unmarked cookies are.
What do the prefixSecurity options do in tough-cookie?
They control what happens when a cookie name starts with __Secure- or __Host- but the attributes do not meet the requirements: silent, the default, drops the cookie without error, strict errors out, and unsafe-disabled turns the check off entirely.
Why does tough-cookie treat localhost as a secure origin?
Because most web browsers treat localhost as a trustworthy origin and tough-cookie follows that default. You can turn it off with the allowSecureOnLocal option set to false, which makes a Secure cookie for localhost persisted on set but not returned on retrieval.
Is RFC6265bis support in tough-cookie finished?
No. The documentation states that support is being developed and that, since the revisions are still in Active Internet-Draft state, the areas of support are subject to change. The project recommends reading RFC6265bis sections 8.8 and 4.1.3 for the details.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/salesforce-tough-cookie)Star history chart for your README
[](https://hysenlabs.com/projects/salesforce-tough-cookie)Was this useful?