├── .eslintignore ├── .eslintrc.yml ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── codeql.yml │ └── scorecard.yml ├── .gitignore ├── HISTORY.md ├── LICENSE ├── README.md ├── index.js ├── package.json └── test ├── .eslintrc.yml └── cookieParser.js /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | node_modules 3 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | root: true 2 | extends: 3 | - standard 4 | - plugin:markdown/recommended 5 | plugins: 6 | - markdown 7 | overrides: 8 | - files: '**/*.md' 9 | processor: 'markdown/markdown' 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: monthly 7 | 8 | - package-ecosystem: npm 9 | directory: / 10 | schedule: 11 | interval: monthly 12 | open-pull-requests-limit: 10 13 | ignore: 14 | - dependency-name: "*" 15 | update-types: ["version-update:semver-major"] 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | - pull_request 5 | - push 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | test: 12 | permissions: 13 | checks: write # for coverallsapp/github-action to create new checks 14 | contents: read # for actions/checkout to fetch code 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | name: 19 | - Node.js 0.8 20 | - Node.js 0.10 21 | - Node.js 0.12 22 | - io.js 1.x 23 | - io.js 2.x 24 | - io.js 3.x 25 | - Node.js 4.x 26 | - Node.js 5.x 27 | - Node.js 6.x 28 | - Node.js 7.x 29 | - Node.js 8.x 30 | - Node.js 9.x 31 | - Node.js 10.x 32 | - Node.js 11.x 33 | - Node.js 12.x 34 | - Node.js 13.x 35 | - Node.js 14.x 36 | - Node.js 15.x 37 | - Node.js 16.x 38 | - Node.js 17.x 39 | - Node.js 18.x 40 | - Node.js 19.x 41 | - Node.js 20.x 42 | - Node.js 21.x 43 | - Node.js 22.x 44 | 45 | include: 46 | - name: Node.js 0.8 47 | node-version: "0.8" 48 | npm-i: mocha@2.5.3 supertest@1.1.0 49 | npm-rm: nyc 50 | 51 | - name: Node.js 0.10 52 | node-version: "0.10" 53 | npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0 54 | 55 | - name: Node.js 0.12 56 | node-version: "0.12" 57 | npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0 58 | 59 | - name: io.js 1.x 60 | node-version: "1.8" 61 | npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0 62 | 63 | - name: io.js 2.x 64 | node-version: "2.5" 65 | npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0 66 | 67 | - name: io.js 3.x 68 | node-version: "3.3" 69 | npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0 70 | 71 | - name: Node.js 4.x 72 | node-version: "4.9" 73 | npm-i: mocha@5.2.0 nyc@11.9.0 supertest@3.4.2 74 | 75 | - name: Node.js 5.x 76 | node-version: "5.12" 77 | npm-i: mocha@5.2.0 nyc@11.9.0 supertest@3.4.2 78 | 79 | - name: Node.js 6.x 80 | node-version: "6.17" 81 | npm-i: mocha@6.2.2 nyc@14.1.1 supertest@3.4.2 82 | 83 | - name: Node.js 7.x 84 | node-version: "7.10" 85 | npm-i: mocha@6.2.2 nyc@14.1.1 86 | 87 | - name: Node.js 8.x 88 | node-version: "8.17" 89 | npm-i: mocha@7.2.0 nyc@14.1.1 90 | 91 | - name: Node.js 9.x 92 | node-version: "9.11" 93 | npm-i: mocha@7.2.0 nyc@14.1.1 94 | 95 | - name: Node.js 10.x 96 | node-version: "10.24" 97 | npm-i: mocha@8.4.0 98 | 99 | - name: Node.js 11.x 100 | node-version: "11.15" 101 | npm-i: mocha@8.4.0 102 | 103 | - name: Node.js 12.x 104 | node-version: "12.22" 105 | 106 | - name: Node.js 13.x 107 | node-version: "13.14" 108 | 109 | - name: Node.js 14.x 110 | node-version: "14.19" 111 | 112 | - name: Node.js 15.x 113 | node-version: "15.14" 114 | 115 | - name: Node.js 16.x 116 | node-version: "16.14" 117 | 118 | - name: Node.js 17.x 119 | node-version: "17.6" 120 | 121 | - name: Node.js 18.x 122 | node-version: "18.14" 123 | 124 | - name: Node.js 19.x 125 | node-version: "19.6" 126 | 127 | - name: Node.js 20.x 128 | node-version: "20.12" 129 | 130 | - name: Node.js 21.x 131 | node-version: "21.7" 132 | 133 | - name: Node.js 22.x 134 | node-version: "22.0" 135 | 136 | steps: 137 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 138 | 139 | - name: Install Node.js ${{ matrix.node-version }} 140 | shell: bash -eo pipefail -l {0} 141 | run: | 142 | nvm install --default ${{ matrix.node-version }} 143 | if [[ "${{ matrix.node-version }}" == 0.* && "$(cut -d. -f2 <<< "${{ matrix.node-version }}")" -lt 10 ]]; then 144 | nvm install --alias=npm 0.10 145 | nvm use ${{ matrix.node-version }} 146 | sed -i '1s;^.*$;'"$(printf '#!%q' "$(nvm which npm)")"';' "$(readlink -f "$(which npm)")" 147 | npm config set strict-ssl false 148 | fi 149 | dirname "$(nvm which ${{ matrix.node-version }})" >> "$GITHUB_PATH" 150 | 151 | - name: Configure npm 152 | run: | 153 | if [[ "$(npm config get package-lock)" == "true" ]]; then 154 | npm config set package-lock false 155 | else 156 | npm config set shrinkwrap false 157 | fi 158 | 159 | - name: Remove npm module(s) ${{ matrix.npm-rm }} 160 | run: npm rm --silent --save-dev ${{ matrix.npm-rm }} 161 | if: matrix.npm-rm != '' 162 | 163 | - name: Install npm module(s) ${{ matrix.npm-i }} 164 | run: npm install --save-dev ${{ matrix.npm-i }} 165 | if: matrix.npm-i != '' 166 | 167 | - name: Setup Node.js version-specific dependencies 168 | shell: bash 169 | run: | 170 | # eslint for linting 171 | # - remove on Node.js < 10 172 | if [[ "$(cut -d. -f1 <<< "${{ matrix.node-version }}")" -lt 10 ]]; then 173 | node -pe 'Object.keys(require("./package").devDependencies).join("\n")' | \ 174 | grep -E '^eslint(-|$)' | \ 175 | sort -r | \ 176 | xargs -n1 npm rm --silent --save-dev 177 | fi 178 | 179 | - name: Install Node.js dependencies 180 | run: npm install 181 | 182 | - name: List environment 183 | id: list_env 184 | shell: bash 185 | run: | 186 | echo "node@$(node -v)" 187 | echo "npm@$(npm -v)" 188 | npm -s ls ||: 189 | (npm -s ls --depth=0 ||:) | awk -F'[ @]' 'NR>1 && $2 { print "" $2 "=" $3 }' >> "$GITHUB_OUTPUT" 190 | 191 | - name: Run tests 192 | shell: bash 193 | run: | 194 | if npm -ps ls nyc | grep -q nyc; then 195 | npm run test-ci 196 | else 197 | npm test 198 | fi 199 | 200 | - name: Lint code 201 | if: steps.list_env.outputs.eslint != '' 202 | run: npm run lint 203 | 204 | - name: Collect code coverage 205 | uses: coverallsapp/github-action@09b709cf6a16e30b0808ba050c7a6e8a5ef13f8d # master 206 | if: steps.list_env.outputs.nyc != '' 207 | with: 208 | github-token: ${{ secrets.GITHUB_TOKEN }} 209 | flag-name: run-${{ matrix.test_number }} 210 | parallel: true 211 | 212 | coverage: 213 | permissions: 214 | checks: write # for coverallsapp/github-action to create new checks 215 | needs: test 216 | runs-on: ubuntu-latest 217 | steps: 218 | - name: Upload code coverage 219 | uses: coverallsapp/github-action@09b709cf6a16e30b0808ba050c7a6e8a5ef13f8d # master 220 | with: 221 | github-token: ${{ secrets.github_token }} 222 | parallel-finished: true 223 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: ["master"] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: ["master"] 20 | schedule: 21 | - cron: "0 0 * * 1" 22 | 23 | permissions: 24 | contents: read 25 | 26 | jobs: 27 | analyze: 28 | name: Analyze 29 | runs-on: ubuntu-latest 30 | permissions: 31 | actions: read 32 | contents: read 33 | security-events: write 34 | 35 | steps: 36 | - name: Checkout repository 37 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 38 | 39 | # Initializes the CodeQL tools for scanning. 40 | - name: Initialize CodeQL 41 | uses: github/codeql-action/init@3ab4101902695724f9365a384f86c1074d94e18c # v3.24.7 42 | with: 43 | languages: javascript 44 | # If you wish to specify custom queries, you can do so here or in a config file. 45 | # By default, queries listed here will override any specified in a config file. 46 | # Prefix the list here with "+" to use these queries and those in the config file. 47 | 48 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 49 | # If this step fails, then you should remove it and run the build manually (see below) 50 | # - name: Autobuild 51 | # uses: github/codeql-action/autobuild@3ab4101902695724f9365a384f86c1074d94e18c # v3.24.7 52 | 53 | # ℹ️ Command-line programs to run using the OS shell. 54 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 55 | 56 | # If the Autobuild fails above, remove it and uncomment the following three lines. 57 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 58 | 59 | # - run: | 60 | # echo "Run, Build Application using script" 61 | # ./location_of_script_within_repo/buildscript.sh 62 | 63 | - name: Perform CodeQL Analysis 64 | uses: github/codeql-action/analyze@3ab4101902695724f9365a384f86c1074d94e18c # v3.24.7 65 | with: 66 | category: "/language:javascript" -------------------------------------------------------------------------------- /.github/workflows/scorecard.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. They are provided 2 | # by a third-party and are governed by separate terms of service, privacy 3 | # policy, and support documentation. 4 | 5 | name: Scorecard supply-chain security 6 | 7 | on: 8 | # For Branch-Protection check. Only the default branch is supported. See 9 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 10 | branch_protection_rule: 11 | # To guarantee Maintained check is occasionally updated. See 12 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained 13 | schedule: 14 | - cron: '16 21 * * 1' 15 | push: 16 | branches: [ "master" ] 17 | 18 | # Declare default permissions as read only. 19 | permissions: read-all 20 | 21 | jobs: 22 | analysis: 23 | name: Scorecard analysis 24 | runs-on: ubuntu-latest 25 | permissions: 26 | # Needed to upload the results to code-scanning dashboard. 27 | security-events: write 28 | # Needed to publish results and get a badge (see publish_results below). 29 | id-token: write 30 | # Uncomment the permissions below if installing in a private repository. 31 | # contents: read 32 | # actions: read 33 | 34 | steps: 35 | - name: "Checkout code" 36 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.2 37 | with: 38 | persist-credentials: false 39 | 40 | - name: "Run analysis" 41 | uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 42 | with: 43 | results_file: results.sarif 44 | results_format: sarif 45 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: 46 | # - you want to enable the Branch-Protection check on a *public* repository, or 47 | # - you are installing Scorecard on a *private* repository 48 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. 49 | # repo_token: ${{ secrets.SCORECARD_TOKEN }} 50 | 51 | # Public repositories: 52 | # - Publish results to OpenSSF REST API for easy access by consumers 53 | # - Allows the repository to include the Scorecard badge. 54 | # - See https://github.com/ossf/scorecard-action#publishing-results. 55 | # For private repositories: 56 | # - `publish_results` will always be set to `false`, regardless 57 | # of the value entered here. 58 | publish_results: true 59 | 60 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 61 | # format to the repository Actions tab. 62 | - name: "Upload artifact" 63 | uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 64 | with: 65 | name: SARIF file 66 | path: results.sarif 67 | retention-days: 5 68 | 69 | # Upload the results to GitHub's code scanning dashboard. 70 | - name: "Upload to code-scanning" 71 | uses: github/codeql-action/upload-sarif@2f93e4319b2f04a2efc38fa7f78bd681bc3f7b2f # v2.23.2 72 | with: 73 | sarif_file: results.sarif 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | coverage 3 | node_modules 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | 1.4.7 / 2024-10-08 2 | ========== 3 | 4 | * deps: cookie@0.7.2 5 | - Fix object assignment of `hasOwnProperty` 6 | * deps: cookie@0.7.1 7 | - Allow leading dot for domain 8 | - Although not permitted in the spec, some users expect this to work and user agents ignore the leading dot according to spec 9 | - Add fast path for `serialize` without options, use `obj.hasOwnProperty` when parsing 10 | * deps: cookie@0.7.0 11 | - perf: parse cookies ~10% faster 12 | - fix: narrow the validation of cookies to match RFC6265 13 | - fix: add `main` to `package.json` for rspack 14 | * deps: cookie@0.6.0 15 | - Add `partitioned` option 16 | * deps: cookie@0.5.0 17 | - Add `priority` option 18 | - Fix `expires` option to reject invalid dates 19 | - pref: improve default decode speed 20 | - pref: remove slow string split in parse 21 | * deps: cookie@0.4.2 22 | - pref: read value only when assigning in parse 23 | - pref: remove unnecessary regexp in parse 24 | 25 | 1.4.6 / 2021-11-16 26 | ================== 27 | 28 | * deps: cookie@0.4.1 29 | 30 | 1.4.5 / 2020-03-14 31 | ================== 32 | 33 | * deps: cookie@0.4.0 34 | 35 | 1.4.4 / 2019-02-12 36 | ================== 37 | 38 | * perf: normalize `secret` argument only once 39 | 40 | 1.4.3 / 2016-05-26 41 | ================== 42 | 43 | * deps: cookie@0.3.1 44 | - perf: use for loop in parse 45 | 46 | 1.4.2 / 2016-05-20 47 | ================== 48 | 49 | * deps: cookie@0.2.4 50 | - perf: enable strict mode 51 | - perf: use for loop in parse 52 | - perf: use string concatenation for serialization 53 | 54 | 1.4.1 / 2016-01-11 55 | ================== 56 | 57 | * deps: cookie@0.2.3 58 | * perf: enable strict mode 59 | 60 | 1.4.0 / 2015-09-18 61 | ================== 62 | 63 | * Accept array of secrets in addition to a single secret 64 | * Fix `JSONCookie` to return `undefined` for non-string arguments 65 | * Fix `signedCookie` to return `undefined` for non-string arguments 66 | * deps: cookie@0.2.2 67 | 68 | 1.3.5 / 2015-05-19 69 | ================== 70 | 71 | * deps: cookie@0.1.3 72 | - Slight optimizations 73 | 74 | 1.3.4 / 2015-02-15 75 | ================== 76 | 77 | * deps: cookie-signature@1.0.6 78 | 79 | 1.3.3 / 2014-09-05 80 | ================== 81 | 82 | * deps: cookie-signature@1.0.5 83 | 84 | 1.3.2 / 2014-06-26 85 | ================== 86 | 87 | * deps: cookie-signature@1.0.4 88 | - fix for timing attacks 89 | 90 | 1.3.1 / 2014-06-17 91 | ================== 92 | 93 | * actually export `signedCookie` 94 | 95 | 1.3.0 / 2014-06-17 96 | ================== 97 | 98 | * add `signedCookie` export for single cookie unsigning 99 | 100 | 1.2.0 / 2014-06-17 101 | ================== 102 | 103 | * export parsing functions 104 | * `req.cookies` and `req.signedCookies` are now plain objects 105 | * slightly faster parsing of many cookies 106 | 107 | 1.1.0 / 2014-05-12 108 | ================== 109 | 110 | * Support for NodeJS version 0.8 111 | * deps: cookie@0.1.2 112 | - Fix for maxAge == 0 113 | - made compat with expires field 114 | - tweak maxAge NaN error message 115 | 116 | 1.0.1 / 2014-02-20 117 | ================== 118 | 119 | * add missing dependencies 120 | 121 | 1.0.0 / 2014-02-15 122 | ================== 123 | 124 | * Genesis from `connect` 125 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2014 TJ Holowaychuk 4 | Copyright (c) 2015 Douglas Christopher Wilson 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | 'Software'), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cookie-parser 2 | 3 | [![NPM Version][npm-version-image]][npm-url] 4 | [![NPM Downloads][npm-downloads-image]][npm-url] 5 | [![Build Status][ci-image]][ci-url] 6 | [![Test Coverage][coveralls-image]][coveralls-url] 7 | 8 | Parse `Cookie` header and populate `req.cookies` with an object keyed by the 9 | cookie names. Optionally you may enable signed cookie support by passing a 10 | `secret` string, which assigns `req.secret` so it may be used by other 11 | middleware. 12 | 13 | ## Installation 14 | 15 | ```sh 16 | $ npm install cookie-parser 17 | ``` 18 | 19 | ## API 20 | 21 | ```js 22 | var cookieParser = require('cookie-parser') 23 | ``` 24 | 25 | ### cookieParser(secret, options) 26 | 27 | Create a new cookie parser middleware function using the given `secret` and 28 | `options`. 29 | 30 | - `secret` a string or array used for signing cookies. This is optional and if 31 | not specified, will not parse signed cookies. If a string is provided, this 32 | is used as the secret. If an array is provided, an attempt will be made to 33 | unsign the cookie with each secret in order. 34 | - `options` an object that is passed to `cookie.parse` as the second option. See 35 | [cookie](https://www.npmjs.org/package/cookie) for more information. 36 | - `decode` a function to decode the value of the cookie 37 | 38 | The middleware will parse the `Cookie` header on the request and expose the 39 | cookie data as the property `req.cookies` and, if a `secret` was provided, as 40 | the property `req.signedCookies`. These properties are name value pairs of the 41 | cookie name to cookie value. 42 | 43 | When `secret` is provided, this module will unsign and validate any signed cookie 44 | values and move those name value pairs from `req.cookies` into `req.signedCookies`. 45 | A signed cookie is a cookie that has a value prefixed with `s:`. Signed cookies 46 | that fail signature validation will have the value `false` instead of the tampered 47 | value. 48 | 49 | In addition, this module supports special "JSON cookies". These are cookie where 50 | the value is prefixed with `j:`. When these values are encountered, the value will 51 | be exposed as the result of `JSON.parse`. If parsing fails, the original value will 52 | remain. 53 | 54 | ### cookieParser.JSONCookie(str) 55 | 56 | Parse a cookie value as a JSON cookie. This will return the parsed JSON value 57 | if it was a JSON cookie, otherwise, it will return the passed value. 58 | 59 | ### cookieParser.JSONCookies(cookies) 60 | 61 | Given an object, this will iterate over the keys and call `JSONCookie` on each 62 | value, replacing the original value with the parsed value. This returns the 63 | same object that was passed in. 64 | 65 | ### cookieParser.signedCookie(str, secret) 66 | 67 | Parse a cookie value as a signed cookie. This will return the parsed unsigned 68 | value if it was a signed cookie and the signature was valid. If the value was 69 | not signed, the original value is returned. If the value was signed but the 70 | signature could not be validated, `false` is returned. 71 | 72 | The `secret` argument can be an array or string. If a string is provided, this 73 | is used as the secret. If an array is provided, an attempt will be made to 74 | unsign the cookie with each secret in order. 75 | 76 | ### cookieParser.signedCookies(cookies, secret) 77 | 78 | Given an object, this will iterate over the keys and check if any value is a 79 | signed cookie. If it is a signed cookie and the signature is valid, the key 80 | will be deleted from the object and added to the new object that is returned. 81 | 82 | The `secret` argument can be an array or string. If a string is provided, this 83 | is used as the secret. If an array is provided, an attempt will be made to 84 | unsign the cookie with each secret in order. 85 | 86 | ## Example 87 | 88 | ```js 89 | var express = require('express') 90 | var cookieParser = require('cookie-parser') 91 | 92 | var app = express() 93 | app.use(cookieParser()) 94 | 95 | app.get('/', function (req, res) { 96 | // Cookies that have not been signed 97 | console.log('Cookies: ', req.cookies) 98 | 99 | // Cookies that have been signed 100 | console.log('Signed Cookies: ', req.signedCookies) 101 | }) 102 | 103 | app.listen(8080) 104 | 105 | // curl command that sends an HTTP request with two cookies 106 | // curl http://127.0.0.1:8080 --cookie "Cho=Kim;Greet=Hello" 107 | ``` 108 | 109 | ## License 110 | 111 | [MIT](LICENSE) 112 | 113 | [ci-image]: https://badgen.net/github/checks/expressjs/cookie-parser/master?label=ci 114 | [ci-url]: https://github.com/expressjs/cookie-parser/actions?query=workflow%3Aci 115 | [coveralls-image]: https://badgen.net/coveralls/c/github/expressjs/cookie-parser/master 116 | [coveralls-url]: https://coveralls.io/r/expressjs/cookie-parser?branch=master 117 | [npm-downloads-image]: https://badgen.net/npm/dm/cookie-parser 118 | [npm-url]: https://npmjs.org/package/cookie-parser 119 | [npm-version-image]: https://badgen.net/npm/v/cookie-parser 120 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * cookie-parser 3 | * Copyright(c) 2014 TJ Holowaychuk 4 | * Copyright(c) 2015 Douglas Christopher Wilson 5 | * MIT Licensed 6 | */ 7 | 8 | 'use strict' 9 | 10 | /** 11 | * Module dependencies. 12 | * @private 13 | */ 14 | 15 | var cookie = require('cookie') 16 | var signature = require('cookie-signature') 17 | 18 | /** 19 | * Module exports. 20 | * @public 21 | */ 22 | 23 | module.exports = cookieParser 24 | module.exports.JSONCookie = JSONCookie 25 | module.exports.JSONCookies = JSONCookies 26 | module.exports.signedCookie = signedCookie 27 | module.exports.signedCookies = signedCookies 28 | 29 | /** 30 | * Parse Cookie header and populate `req.cookies` 31 | * with an object keyed by the cookie names. 32 | * 33 | * @param {string|array} [secret] A string (or array of strings) representing cookie signing secret(s). 34 | * @param {Object} [options] 35 | * @return {Function} 36 | * @public 37 | */ 38 | 39 | function cookieParser (secret, options) { 40 | var secrets = !secret || Array.isArray(secret) 41 | ? (secret || []) 42 | : [secret] 43 | 44 | return function cookieParser (req, res, next) { 45 | if (req.cookies) { 46 | return next() 47 | } 48 | 49 | var cookies = req.headers.cookie 50 | 51 | req.secret = secrets[0] 52 | req.cookies = Object.create(null) 53 | req.signedCookies = Object.create(null) 54 | 55 | // no cookies 56 | if (!cookies) { 57 | return next() 58 | } 59 | 60 | req.cookies = cookie.parse(cookies, options) 61 | 62 | // parse signed cookies 63 | if (secrets.length !== 0) { 64 | req.signedCookies = signedCookies(req.cookies, secrets) 65 | req.signedCookies = JSONCookies(req.signedCookies) 66 | } 67 | 68 | // parse JSON cookies 69 | req.cookies = JSONCookies(req.cookies) 70 | 71 | next() 72 | } 73 | } 74 | 75 | /** 76 | * Parse JSON cookie string. 77 | * 78 | * @param {String} str 79 | * @return {Object} Parsed object or undefined if not json cookie 80 | * @public 81 | */ 82 | 83 | function JSONCookie (str) { 84 | if (typeof str !== 'string' || str.substr(0, 2) !== 'j:') { 85 | return undefined 86 | } 87 | 88 | try { 89 | return JSON.parse(str.slice(2)) 90 | } catch (err) { 91 | return undefined 92 | } 93 | } 94 | 95 | /** 96 | * Parse JSON cookies. 97 | * 98 | * @param {Object} obj 99 | * @return {Object} 100 | * @public 101 | */ 102 | 103 | function JSONCookies (obj) { 104 | var cookies = Object.keys(obj) 105 | var key 106 | var val 107 | 108 | for (var i = 0; i < cookies.length; i++) { 109 | key = cookies[i] 110 | val = JSONCookie(obj[key]) 111 | 112 | if (val) { 113 | obj[key] = val 114 | } 115 | } 116 | 117 | return obj 118 | } 119 | 120 | /** 121 | * Parse a signed cookie string, return the decoded value. 122 | * 123 | * @param {String} str signed cookie string 124 | * @param {string|array} secret 125 | * @return {String} decoded value 126 | * @public 127 | */ 128 | 129 | function signedCookie (str, secret) { 130 | if (typeof str !== 'string') { 131 | return undefined 132 | } 133 | 134 | if (str.substr(0, 2) !== 's:') { 135 | return str 136 | } 137 | 138 | var secrets = !secret || Array.isArray(secret) 139 | ? (secret || []) 140 | : [secret] 141 | 142 | for (var i = 0; i < secrets.length; i++) { 143 | var val = signature.unsign(str.slice(2), secrets[i]) 144 | 145 | if (val !== false) { 146 | return val 147 | } 148 | } 149 | 150 | return false 151 | } 152 | 153 | /** 154 | * Parse signed cookies, returning an object containing the decoded key/value 155 | * pairs, while removing the signed key from obj. 156 | * 157 | * @param {Object} obj 158 | * @param {string|array} secret 159 | * @return {Object} 160 | * @public 161 | */ 162 | 163 | function signedCookies (obj, secret) { 164 | var cookies = Object.keys(obj) 165 | var dec 166 | var key 167 | var ret = Object.create(null) 168 | var val 169 | 170 | for (var i = 0; i < cookies.length; i++) { 171 | key = cookies[i] 172 | val = obj[key] 173 | dec = signedCookie(val, secret) 174 | 175 | if (val !== dec) { 176 | ret[key] = dec 177 | delete obj[key] 178 | } 179 | } 180 | 181 | return ret 182 | } 183 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cookie-parser", 3 | "description": "Parse HTTP request cookies", 4 | "version": "1.4.7", 5 | "author": "TJ Holowaychuk (http://tjholowaychuk.com)", 6 | "contributors": [ 7 | "Douglas Christopher Wilson " 8 | ], 9 | "license": "MIT", 10 | "repository": "expressjs/cookie-parser", 11 | "keywords": [ 12 | "cookie", 13 | "middleware" 14 | ], 15 | "dependencies": { 16 | "cookie": "0.7.2", 17 | "cookie-signature": "1.0.6" 18 | }, 19 | "devDependencies": { 20 | "eslint": "7.32.0", 21 | "eslint-config-standard": "14.1.1", 22 | "eslint-plugin-import": "2.25.2", 23 | "eslint-plugin-markdown": "2.2.1", 24 | "eslint-plugin-node": "11.1.0", 25 | "eslint-plugin-promise": "4.3.1", 26 | "eslint-plugin-standard": "4.1.0", 27 | "mocha": "^9.2.2", 28 | "nyc": "15.1.0", 29 | "supertest": "6.1.6" 30 | }, 31 | "files": [ 32 | "LICENSE", 33 | "HISTORY.md", 34 | "index.js" 35 | ], 36 | "engines": { 37 | "node": ">= 0.8.0" 38 | }, 39 | "scripts": { 40 | "lint": "eslint .", 41 | "test": "mocha --reporter spec --bail --check-leaks test/", 42 | "test-ci": "nyc --reporter=lcov --reporter=text npm test", 43 | "test-cov": "nyc --reporter=html --reporter=text npm test" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | mocha: true 3 | -------------------------------------------------------------------------------- /test/cookieParser.js: -------------------------------------------------------------------------------- 1 | 2 | var assert = require('assert') 3 | var cookieParser = require('..') 4 | var http = require('http') 5 | var request = require('supertest') 6 | var signature = require('cookie-signature') 7 | 8 | describe('cookieParser()', function () { 9 | it('should export JSONCookies function', function () { 10 | assert(typeof cookieParser.JSONCookies, 'function') 11 | }) 12 | 13 | describe('when no cookies are sent', function () { 14 | it('should default req.cookies to {}', function (done) { 15 | request(createServer('keyboard cat')) 16 | .get('/') 17 | .expect(200, '{}', done) 18 | }) 19 | 20 | it('should default req.signedCookies to {}', function (done) { 21 | request(createServer('keyboard cat')) 22 | .get('/signed') 23 | .expect(200, '{}', done) 24 | }) 25 | }) 26 | 27 | describe('when cookies are sent', function () { 28 | it('should populate req.cookies', function (done) { 29 | request(createServer('keyboard cat')) 30 | .get('/') 31 | .set('Cookie', 'foo=bar; bar=baz') 32 | .expect(200, '{"foo":"bar","bar":"baz"}', done) 33 | }) 34 | 35 | it('should inflate JSON cookies', function (done) { 36 | request(createServer('keyboard cat')) 37 | .get('/') 38 | .set('Cookie', 'foo=j:{"foo":"bar"}') 39 | .expect(200, '{"foo":{"foo":"bar"}}', done) 40 | }) 41 | 42 | it('should not inflate invalid JSON cookies', function (done) { 43 | request(createServer('keyboard cat')) 44 | .get('/') 45 | .set('Cookie', 'foo=j:{"foo":') 46 | .expect(200, '{"foo":"j:{\\"foo\\":"}', done) 47 | }) 48 | }) 49 | 50 | describe('when req.cookies exists', function () { 51 | it('should do nothing', function (done) { 52 | var _parser = cookieParser() 53 | var server = http.createServer(function (req, res) { 54 | req.cookies = { fizz: 'buzz' } 55 | _parser(req, res, function (err) { 56 | if (err) { 57 | res.statusCode = 500 58 | res.end(err.message) 59 | return 60 | } 61 | 62 | res.end(JSON.stringify(req.cookies)) 63 | }) 64 | }) 65 | 66 | request(server) 67 | .get('/') 68 | .set('Cookie', 'foo=bar; bar=baz') 69 | .expect(200, '{"fizz":"buzz"}', done) 70 | }) 71 | }) 72 | 73 | describe('when a secret is given', function () { 74 | var val = signature.sign('foobarbaz', 'keyboard cat') 75 | // TODO: "bar" fails... 76 | 77 | it('should populate req.signedCookies', function (done) { 78 | request(createServer('keyboard cat')) 79 | .get('/signed') 80 | .set('Cookie', 'foo=s:' + val) 81 | .expect(200, '{"foo":"foobarbaz"}', done) 82 | }) 83 | 84 | it('should remove the signed value from req.cookies', function (done) { 85 | request(createServer('keyboard cat')) 86 | .get('/') 87 | .set('Cookie', 'foo=s:' + val) 88 | .expect(200, '{}', done) 89 | }) 90 | 91 | it('should omit invalid signatures', function (done) { 92 | var server = createServer('keyboard cat') 93 | 94 | request(server) 95 | .get('/signed') 96 | .set('Cookie', 'foo=' + val + '3') 97 | .expect(200, '{}', function (err) { 98 | if (err) return done(err) 99 | request(server) 100 | .get('/') 101 | .set('Cookie', 'foo=' + val + '3') 102 | .expect(200, '{"foo":"foobarbaz.CP7AWaXDfAKIRfH49dQzKJx7sKzzSoPq7/AcBBRVwlI3"}', done) 103 | }) 104 | }) 105 | }) 106 | 107 | describe('when multiple secrets are given', function () { 108 | it('should populate req.signedCookies', function (done) { 109 | request(createServer(['keyboard cat', 'nyan cat'])) 110 | .get('/signed') 111 | .set('Cookie', 'buzz=s:foobar.N5r0C3M8W+IPpzyAJaIddMWbTGfDSO+bfKlZErJ+MeE; fizz=s:foobar.JTCAgiMWsnuZpN3mrYnEUjXlGxmDi4POCBnWbRxse88') 112 | .expect(200, '{"buzz":"foobar","fizz":"foobar"}', done) 113 | }) 114 | }) 115 | 116 | describe('when no secret is given', function () { 117 | var server 118 | before(function () { 119 | server = createServer() 120 | }) 121 | 122 | it('should populate req.cookies', function (done) { 123 | request(server) 124 | .get('/') 125 | .set('Cookie', 'foo=bar; bar=baz') 126 | .expect(200, '{"foo":"bar","bar":"baz"}', done) 127 | }) 128 | 129 | it('should not populate req.signedCookies', function (done) { 130 | var val = signature.sign('foobarbaz', 'keyboard cat') 131 | request(server) 132 | .get('/signed') 133 | .set('Cookie', 'foo=s:' + val) 134 | .expect(200, '{}', done) 135 | }) 136 | }) 137 | }) 138 | 139 | describe('cookieParser.JSONCookie(str)', function () { 140 | it('should return undefined for non-string arguments', function () { 141 | assert.strictEqual(cookieParser.JSONCookie(), undefined) 142 | assert.strictEqual(cookieParser.JSONCookie(undefined), undefined) 143 | assert.strictEqual(cookieParser.JSONCookie(null), undefined) 144 | assert.strictEqual(cookieParser.JSONCookie(42), undefined) 145 | assert.strictEqual(cookieParser.JSONCookie({}), undefined) 146 | assert.strictEqual(cookieParser.JSONCookie([]), undefined) 147 | assert.strictEqual(cookieParser.JSONCookie(function () {}), undefined) 148 | }) 149 | 150 | it('should return undefined for non-JSON cookie string', function () { 151 | assert.strictEqual(cookieParser.JSONCookie(''), undefined) 152 | assert.strictEqual(cookieParser.JSONCookie('foo'), undefined) 153 | assert.strictEqual(cookieParser.JSONCookie('{}'), undefined) 154 | }) 155 | 156 | it('should return object for JSON cookie string', function () { 157 | assert.deepEqual(cookieParser.JSONCookie('j:{"foo":"bar"}'), { foo: 'bar' }) 158 | }) 159 | 160 | it('should return undefined on invalid JSON', function () { 161 | assert.strictEqual(cookieParser.JSONCookie('j:{foo:"bar"}'), undefined) 162 | }) 163 | }) 164 | 165 | describe('cookieParser.signedCookie(str, secret)', function () { 166 | it('should return undefined for non-string arguments', function () { 167 | assert.strictEqual(cookieParser.signedCookie(undefined, 'keyboard cat'), undefined) 168 | assert.strictEqual(cookieParser.signedCookie(null, 'keyboard cat'), undefined) 169 | assert.strictEqual(cookieParser.signedCookie(42, 'keyboard cat'), undefined) 170 | assert.strictEqual(cookieParser.signedCookie({}, 'keyboard cat'), undefined) 171 | assert.strictEqual(cookieParser.signedCookie([], 'keyboard cat'), undefined) 172 | assert.strictEqual(cookieParser.signedCookie(function () {}, 'keyboard cat'), undefined) 173 | }) 174 | 175 | it('should pass through non-signed string', function () { 176 | assert.strictEqual(cookieParser.signedCookie('', 'keyboard cat'), '') 177 | assert.strictEqual(cookieParser.signedCookie('foo', 'keyboard cat'), 'foo') 178 | assert.strictEqual(cookieParser.signedCookie('j:{}', 'keyboard cat'), 'j:{}') 179 | }) 180 | 181 | it('should return false for tampered signed string', function () { 182 | assert.strictEqual(cookieParser.signedCookie('s:foobaz.N5r0C3M8W+IPpzyAJaIddMWbTGfDSO+bfKlZErJ+MeE', 'keyboard cat'), false) 183 | }) 184 | 185 | it('should return unsigned value for signed string', function () { 186 | assert.strictEqual(cookieParser.signedCookie('s:foobar.N5r0C3M8W+IPpzyAJaIddMWbTGfDSO+bfKlZErJ+MeE', 'keyboard cat'), 'foobar') 187 | }) 188 | 189 | describe('when secret is an array', function () { 190 | it('should return false for tampered signed string', function () { 191 | assert.strictEqual(cookieParser.signedCookie('s:foobaz.N5r0C3M8W+IPpzyAJaIddMWbTGfDSO+bfKlZErJ+MeE', [ 192 | 'keyboard cat', 193 | 'nyan cat' 194 | ]), false) 195 | }) 196 | 197 | it('should return unsigned value for first secret', function () { 198 | assert.strictEqual(cookieParser.signedCookie('s:foobar.N5r0C3M8W+IPpzyAJaIddMWbTGfDSO+bfKlZErJ+MeE', [ 199 | 'keyboard cat', 200 | 'nyan cat' 201 | ]), 'foobar') 202 | }) 203 | 204 | it('should return unsigned value for second secret', function () { 205 | assert.strictEqual(cookieParser.signedCookie('s:foobar.JTCAgiMWsnuZpN3mrYnEUjXlGxmDi4POCBnWbRxse88', [ 206 | 'keyboard cat', 207 | 'nyan cat' 208 | ]), 'foobar') 209 | }) 210 | }) 211 | }) 212 | 213 | describe('cookieParser.signedCookies(obj, secret)', function () { 214 | it('should ignore non-signed strings', function () { 215 | assert.deepEqual(cookieParser.signedCookies({}, 'keyboard cat'), {}) 216 | assert.deepEqual(cookieParser.signedCookies({ foo: 'bar' }, 'keyboard cat'), {}) 217 | }) 218 | 219 | it('should include tampered strings as false', function () { 220 | assert.deepEqual(cookieParser.signedCookies({ foo: 's:foobaz.N5r0C3M8W+IPpzyAJaIddMWbTGfDSO+bfKlZErJ+MeE' }, 'keyboard cat'), { 221 | foo: false 222 | }) 223 | }) 224 | 225 | it('should include unsigned strings', function () { 226 | assert.deepEqual(cookieParser.signedCookies({ foo: 's:foobar.N5r0C3M8W+IPpzyAJaIddMWbTGfDSO+bfKlZErJ+MeE' }, 'keyboard cat'), { 227 | foo: 'foobar' 228 | }) 229 | }) 230 | 231 | it('should remove signed strings from original object', function () { 232 | var obj = { 233 | foo: 's:foobar.N5r0C3M8W+IPpzyAJaIddMWbTGfDSO+bfKlZErJ+MeE' 234 | } 235 | 236 | assert.deepEqual(cookieParser.signedCookies(obj, 'keyboard cat'), { foo: 'foobar' }) 237 | assert.deepEqual(obj, {}) 238 | }) 239 | 240 | it('should remove tampered strings from original object', function () { 241 | var obj = { 242 | foo: 's:foobaz.N5r0C3M8W+IPpzyAJaIddMWbTGfDSO+bfKlZErJ+MeE' 243 | } 244 | 245 | assert.deepEqual(cookieParser.signedCookies(obj, 'keyboard cat'), { foo: false }) 246 | assert.deepEqual(obj, {}) 247 | }) 248 | 249 | it('should leave unsigned string in original object', function () { 250 | var obj = { 251 | fizz: 'buzz', 252 | foo: 's:foobar.N5r0C3M8W+IPpzyAJaIddMWbTGfDSO+bfKlZErJ+MeE' 253 | } 254 | 255 | assert.deepEqual(cookieParser.signedCookies(obj, 'keyboard cat'), { foo: 'foobar' }) 256 | assert.deepEqual(obj, { fizz: 'buzz' }) 257 | }) 258 | 259 | describe('when secret is an array', function () { 260 | it('should include unsigned strings for matching secrets', function () { 261 | var obj = { 262 | buzz: 's:foobar.N5r0C3M8W+IPpzyAJaIddMWbTGfDSO+bfKlZErJ+MeE', 263 | fizz: 's:foobar.JTCAgiMWsnuZpN3mrYnEUjXlGxmDi4POCBnWbRxse88' 264 | } 265 | 266 | assert.deepEqual(cookieParser.signedCookies(obj, ['keyboard cat']), { 267 | buzz: 'foobar', 268 | fizz: false 269 | }) 270 | }) 271 | 272 | it('should include unsigned strings for all secrets', function () { 273 | var obj = { 274 | buzz: 's:foobar.N5r0C3M8W+IPpzyAJaIddMWbTGfDSO+bfKlZErJ+MeE', 275 | fizz: 's:foobar.JTCAgiMWsnuZpN3mrYnEUjXlGxmDi4POCBnWbRxse88' 276 | } 277 | 278 | assert.deepEqual(cookieParser.signedCookies(obj, ['keyboard cat', 'nyan cat']), { 279 | buzz: 'foobar', 280 | fizz: 'foobar' 281 | }) 282 | }) 283 | }) 284 | }) 285 | 286 | function createServer (secret) { 287 | var _parser = cookieParser(secret) 288 | return http.createServer(function (req, res) { 289 | _parser(req, res, function (err) { 290 | if (err) { 291 | res.statusCode = 500 292 | res.end(err.message) 293 | return 294 | } 295 | 296 | var cookies = req.url === '/signed' 297 | ? req.signedCookies 298 | : req.cookies 299 | res.end(JSON.stringify(cookies)) 300 | }) 301 | }) 302 | } 303 | --------------------------------------------------------------------------------