base on RFC6265 Cookies and CookieJar for Node.js # Tough Cookie · [![RFC6265][rfc6265-badge]][rfc6265-tracker] [![RFC6265bis][rfc6265bis-badge]][rfc6265bis-tracker] [![npm version][npm-badge]][npm-repo] [![CI on Github Actions: salesforce/tough-cookie][ci-badge]][ci-url] ![PRs Welcome][prs-welcome-badge] A Node.js implementation of [RFC6265][rfc6265-tracker] for cookie parsing, storage, and retrieval. ## Getting Started Install Tough Cookie using [`npm`][npm-repo]: ```shell npm install tough-cookie ``` or [`yarn`][yarn-repo]: ```shell yarn add tough-cookie ``` ## Usage ```typescript import { Cookie, CookieJar } from 'tough-cookie' // parse a `Cookie` request header const reqCookies = 'ID=298zf09hf012fh2; csrf=u32t4o3tb3gg43; _gat=1' .split(';') .map(Cookie.parse) // generate a `Cookie` request header const cookieHeader = reqCookies.map((cookie) => cookie.cookieString()).join(';') // parse a Set-Cookie response header const resCookie = Cookie.parse( 'foo=bar; Domain=example.com; Path=/; Expires=Tue, 21 Oct 2025 00:00:00 GMT', ) // generate a Set-Cookie response header const setCookieHeader = cookie.toString() // store and retrieve cookies 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/') ``` > [!IMPORTANT] > For more detailed usage information, refer to the [API docs](./api/docs/tough-cookie.md). ## RFC6265bis Support for [RFC6265bis][rfc6265bis-tracker] is being developed. As these revisions to [RFC6252][rfc6265-tracker] are still in `Active Internet-Draft` state, the areas of support that follow are subject to change. ### SameSite Cookies This change makes it possible for servers, and supporting clients, to mitigate certain types of CSRF attacks by disallowing `SameSite` cookies from being sent cross-origin. #### Example ```typescript import { CookieJar } from 'tough-cookie' const cookieJar = new CookieJar() // uses the in-memory store by default // storing cookies with various SameSite attributes await cookieJar.setCookie( 'strict=authorized; SameSite=strict', 'http://example.com/index.html', ) await cookieJar.setCookie( 'lax=okay; SameSite=lax', 'http://example.com/index.html', ) await cookieJar.setCookie('normal=whatever', 'http://example.com/index.html') // retrieving cookies using a SameSite 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', }) ``` > [!NOTE] > It is highly recommended that you read [RFC6265bis - Section 8.8][samesite-implementation] for more details on SameSite cookies, security considerations, and defense in depth. ### Cookie Prefixes Cookie prefixes are 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-"` If a cookie's name begins with a case-sensitive match for the string `__Secure-`, then the cookie was set with a "Secure" attribute. - `"__Host-"` If a cookie's name begins with a case-sensitive match for the string `__Host-`, then the cookie was set with a "Secure" attribute, a "Path" attribute with a value of "/", and no "Domain" attribute. If `prefixSecurity` is enabled for `CookieJar`, then cookies that match the prefixes defined above but do not obey the attribute restrictions are not added. You can define this functionality by passing in the `prefixSecurity` option to `CookieJar`. It can be one of 3 values: 1. `silent`: (**default**) Enable cookie prefix checking but silently fail to add the cookie if conditions are not met. 2. `strict`: Enable cookie prefix checking and error out if conditions are not met. 3. `unsafe-disabled`: Disable cookie prefix checking. > If `ignoreError` is passed in as `true` when setting a cookie then the error is silent regardless of the `prefixSecurity` option (assuming it's enabled). #### Example ```typescript import { CookieJar, MemoryCookieStore } from 'tough-cookie' const cookieJar = new CookieJar(new MemoryCookieStore(), { prefixSecurity: 'silent', }) // this cookie will be silently ignored since the url is insecure (http) await cookieJar.setCookie( '__Secure-SID=12345; Domain=example.com; Secure;', 'http://example.com', ) // this cookie will be stored since the url is secure (https) await cookieJar.setCookie( '__Secure-SID=12345; Domain=example.com; Secure;', 'https://example.com', ) ``` > [!NOTE] > It is highly recommended that you read [RFC6265bis - Section 4.1.3][cookie-prefixes-implementation] for more details on Cookie Prefixes. ### Potentially Trustworthy Origins are considered "Secure" The definition of a "Secure" connection is not explicitly defined by [RFC6265bis][rfc6265bis-tracker] but the following text is provided in [RFC6265bis - Section 5.8.3][secure-connection-note]: > [!NOTE] > 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 trusted host. As well as a note to [Appendix A. Changes from RFC6265][secure-connection-appendix-a] which refers to **"potentially trustworthy origins"** which are defined in the [Secure Contexts - W3C Candidate Recommendation Draft][potentially-trustworthy-origin]: > [!Note] > Considers potentially trustworthy origins as "secure". Since most web browsers treat `localhost` as a trustworthy origin, by default, so does `tough-cookie`. To disable this behavior, the `CookieStore` must be configured with: ```typescript import { CookieJar, MemoryCookieStore } from 'tough-cookie' 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, }) // this cookie will be persisted to storage await cookieJar.setCookie( 'SID=12345; Domain=localhost; Secure;', 'http://localhost', ) // but, on retrieval, it will not be returned await cookieJar.getCookiesSync('http://localhost') ``` ## Node.js Version Support We follow the [Node.js release schedule](https://github.com/nodejs/Release#release-schedule) and support all versions that are in Active LTS or Maintenance. We will always do a major release when dropping support for older versions of node, and we will do so in consultation with our community. [npm-badge]: https://img.shields.io/npm/v/tough-cookie.svg?style=flat [npm-repo]: https://www.npmjs.com/package/tough-cookie [ci-badge]: https://github.com/salesforce/tough-cookie/actions/workflows/ci.yaml/badge.svg [ci-url]: https://github.com/salesforce/tough-cookie/actions/workflows/ci.yaml [rfc6265-badge]: https://img.shields.io/badge/RFC-6265-flat?labelColor=000000&color=666666 [rfc6265-tracker]: https://datatracker.ietf.org/doc/rfc6265/ [rfc6265bis-badge]: https://img.shields.io/badge/RFC-6265bis-flat?labelColor=000000&color=666666 [rfc6265bis-tracker]: https://datatracker.ietf.org/doc/draft-ietf-httpbis-rfc6265bis/ [samesite-implementation]: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-8.8 [cookie-prefixes-implementation]: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.3 [secure-connection-note]: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-19#section-5.8.3-2.1.2.3.1 [secure-connection-appendix-a]: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-19#appendix-A-1.7.1 [potentially-trustworthy-origin]: https://www.w3.org/TR/secure-contexts/#is-origin-trustworthy [prs-welcome-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg [yarn-repo]: https://yarnpkg.com/package?name=tough-cookie ", Assign "at most 3 tags" to the expected json: {"id":"11771","tags":[]} "only from the tags list I provide: [{"id":77,"name":"3d"},{"id":89,"name":"agent"},{"id":17,"name":"ai"},{"id":54,"name":"algorithm"},{"id":24,"name":"api"},{"id":44,"name":"authentication"},{"id":3,"name":"aws"},{"id":27,"name":"backend"},{"id":60,"name":"benchmark"},{"id":72,"name":"best-practices"},{"id":39,"name":"bitcoin"},{"id":37,"name":"blockchain"},{"id":1,"name":"blog"},{"id":45,"name":"bundler"},{"id":58,"name":"cache"},{"id":21,"name":"chat"},{"id":49,"name":"cicd"},{"id":4,"name":"cli"},{"id":64,"name":"cloud-native"},{"id":48,"name":"cms"},{"id":61,"name":"compiler"},{"id":68,"name":"containerization"},{"id":92,"name":"crm"},{"id":34,"name":"data"},{"id":47,"name":"database"},{"id":8,"name":"declarative-gui "},{"id":9,"name":"deploy-tool"},{"id":53,"name":"desktop-app"},{"id":6,"name":"dev-exp-lib"},{"id":59,"name":"dev-tool"},{"id":13,"name":"ecommerce"},{"id":26,"name":"editor"},{"id":66,"name":"emulator"},{"id":62,"name":"filesystem"},{"id":80,"name":"finance"},{"id":15,"name":"firmware"},{"id":73,"name":"for-fun"},{"id":2,"name":"framework"},{"id":11,"name":"frontend"},{"id":22,"name":"game"},{"id":81,"name":"game-engine "},{"id":23,"name":"graphql"},{"id":84,"name":"gui"},{"id":91,"name":"http"},{"id":5,"name":"http-client"},{"id":51,"name":"iac"},{"id":30,"name":"ide"},{"id":78,"name":"iot"},{"id":40,"name":"json"},{"id":83,"name":"julian"},{"id":38,"name":"k8s"},{"id":31,"name":"language"},{"id":10,"name":"learning-resource"},{"id":33,"name":"lib"},{"id":41,"name":"linter"},{"id":28,"name":"lms"},{"id":16,"name":"logging"},{"id":76,"name":"low-code"},{"id":90,"name":"message-queue"},{"id":42,"name":"mobile-app"},{"id":18,"name":"monitoring"},{"id":36,"name":"networking"},{"id":7,"name":"node-version"},{"id":55,"name":"nosql"},{"id":57,"name":"observability"},{"id":46,"name":"orm"},{"id":52,"name":"os"},{"id":14,"name":"parser"},{"id":74,"name":"react"},{"id":82,"name":"real-time"},{"id":56,"name":"robot"},{"id":65,"name":"runtime"},{"id":32,"name":"sdk"},{"id":71,"name":"search"},{"id":63,"name":"secrets"},{"id":25,"name":"security"},{"id":85,"name":"server"},{"id":86,"name":"serverless"},{"id":70,"name":"storage"},{"id":75,"name":"system-design"},{"id":79,"name":"terminal"},{"id":29,"name":"testing"},{"id":12,"name":"ui"},{"id":50,"name":"ux"},{"id":88,"name":"video"},{"id":20,"name":"web-app"},{"id":35,"name":"web-server"},{"id":43,"name":"webassembly"},{"id":69,"name":"workflow"},{"id":87,"name":"yaml"}]" returns me the "expected json"