{"id":13484174,"url":"https://github.com/fastify/fastify-secure-session","last_synced_at":"2025-05-15T01:06:07.280Z","repository":{"id":37954099,"uuid":"112955255","full_name":"fastify/fastify-secure-session","owner":"fastify","description":"Create a secure stateless cookie session for Fastify","archived":false,"fork":false,"pushed_at":"2025-05-01T19:42:48.000Z","size":271,"stargazers_count":219,"open_issues_count":2,"forks_count":48,"subscribers_count":17,"default_branch":"main","last_synced_at":"2025-05-01T20:33:55.898Z","etag":null,"topics":["fastify","fastify-plugin"],"latest_commit_sha":null,"homepage":"https://npmjs.com/package/@fastify/secure-session","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fastify.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"fastify","open_collective":"fastify"}},"created_at":"2017-12-03T19:06:10.000Z","updated_at":"2025-05-01T19:42:44.000Z","dependencies_parsed_at":"2023-02-09T22:15:25.271Z","dependency_job_id":"88a32ab5-92bf-48d5-afe7-0c0ae31ff88d","html_url":"https://github.com/fastify/fastify-secure-session","commit_stats":{"total_commits":255,"total_committers":45,"mean_commits":5.666666666666667,"dds":0.7607843137254902,"last_synced_commit":"08477809a126a7599b5335ed35382cb192f1b89a"},"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fastify%2Ffastify-secure-session","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fastify%2Ffastify-secure-session/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fastify%2Ffastify-secure-session/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fastify%2Ffastify-secure-session/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fastify","download_url":"https://codeload.github.com/fastify/fastify-secure-session/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254254040,"owners_count":22039792,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fastify","fastify-plugin"],"created_at":"2024-07-31T17:01:20.241Z","updated_at":"2025-05-15T01:06:07.264Z","avatar_url":"https://github.com/fastify.png","language":"JavaScript","readme":"# @fastify/secure-session\n\n[![CI](https://github.com/fastify/fastify-secure-session/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/fastify/fastify-secure-session/actions/workflows/ci.yml)\n[![NPM version](https://img.shields.io/npm/v/@fastify/secure-session.svg?style=flat)](https://www.npmjs.com/package/@fastify/secure-session)\n[![neostandard javascript style](https://img.shields.io/badge/code_style-neostandard-brightgreen?style=flat)](https://github.com/neostandard/neostandard)\n\nCreate a secure stateless cookie session for Fastify, based on libsodium's\n[Secret Key Box Encryption](https://sodium-friends.github.io/docs/docs/secretkeyboxencryption)\nand [@fastify/cookie](https://github.com/fastify/fastify-cookie).\n\n## Using a pregenerated key\n\nFirst generate a key with:\n\n```sh\nnpx @fastify/secure-session \u003e secret-key\n```\n\nIf running in Windows Powershell, you should use this command instead:\n\n```sh\nnpx @fastify/secure-session | Out-File -Encoding default -NoNewline -FilePath secret-key\n```\n\nIf you have not previously used this module with npx, you will be prompted to install it,\nwhich with the output redirect will cause the command to wait forever for input.\n\nTo avoid this use the `--yes` flag with npx:\n\n```sh\nnpx --yes @fastify/secure-session \u003e secret-key\n```\n\nIf you don't want to use `npx`, you can still generate the `secret-key` installing the `@fastify/secure-session` library with your choice package manager, and then:\n\n```sh\n./node_modules/@fastify/secure-session/genkey.js \u003e secret-key\n```\n\nThen, register the plugin as follows:\n\n```js\n'use strict'\n\nconst fastify = require('fastify')({ logger: false })\nconst fs = require('node:fs')\nconst path = require('node:path')\n\nfastify.register(require('@fastify/secure-session'), {\n  // the name of the attribute decorated on the request-object, defaults to 'session'\n  sessionName: 'session',\n  // the name of the session cookie, defaults to value of sessionName\n  cookieName: 'my-session-cookie',\n  // adapt this to point to the directory where secret-key is located\n  key: fs.readFileSync(path.join(__dirname, 'secret-key')),\n  // the amount of time the session is considered valid; this is different from the cookie options\n  // and based on value within the session.\n  expiry: 24 * 60 * 60, // Default 1 day\n  cookie: {\n    path: '/'\n    // options for setCookie, see https://github.com/fastify/fastify-cookie\n  }\n})\n\nfastify.post('/', (request, reply) =\u003e {\n  request.session.set('data', request.body)\n\n  // or when using a custom sessionName:\n  request.customSessionName.set('data', request.body)\n\n  reply.send('hello world')\n})\n\nfastify.get('/', (request, reply) =\u003e {\n  const data = request.session.get('data')\n  if (!data) {\n    reply.code(404).send()\n    return\n  }\n  reply.send(data)\n})\n\nfastify.get('/ping', (request, reply) =\u003e {\n  request.session.options({maxAge: 3600})\n\n  // Send the session cookie to the client even if the session data didn't change\n  // can be used to update cookie expiration\n  request.session.touch()\n  reply.send('pong')\n})\n\nfastify.post('/logout', (request, reply) =\u003e {\n  request.session.delete()\n  reply.send('logged out')\n})\n```\n\nIf you enable [`debug` level logging](https://fastify.dev/docs/latest/Reference/Logging/),\nyou will see what steps the library is taking and understand why a session you\nexpect to be there is not present. For extra details, you can also enable `trace`\nlevel logging.\n\nNote: Instead of using the `get` and `set` methods as seen above, you may also wish to use property getters and setters to make your code compatible with other libraries ie `request.session.data = request.body` and `const data = request.session.data` are also possible. However, if you want to have properties named `changed` or `deleted` in your session data, they can only be accessed via `session.get()` and `session.set()`. (Those are the names of internal properties used by the Session object)\n\n\n### Multiple sessions\n\nIf you want to use multiple sessions, you have to supply an array of options when registering the plugin. It supports the same options as a single session but in this case, the `sessionName` name is mandatory.\n\n```js\nfastify.register(require('@fastify/secure-session'), [{\n  sessionName: 'mySession',\n  cookieName: 'my-session-cookie',\n  key: fs.readFileSync(path.join(__dirname, 'secret-key')),\n  cookie: {\n    path: '/'\n  }\n}, {\n  sessionName: 'myOtherSession',\n  key: fs.readFileSync(path.join(__dirname, 'another-secret-key')),\n  cookie: {\n    path: '/path',\n    maxAge: 100\n  }\n}])\n\nfastify.post('/', (request, reply) =\u003e {\n  request.mySession.set('data', request.body)\n  request.myOtherSession.set('data', request.body)\n  reply.send('hello world')\n})\n```\n\n### Using keys as strings\n\nYou can convert your key file to a hexadecimal string. This is useful in scenarios where you would rather load the key from an environment variable instead of deploying a file.\n\nTo convert a key file into a hexadecimal string you can do this in an npm script:\n\n```js\nconst keyBuffer = fs.readFileSync(path.join(__dirname, 'secret-key'));\nconst hexString = keyBuffer.toString('hex');\nconsole.log(hexString) // Outputs: 4fe91796c30bd989d95b62dc46c7c3ba0b6aa2df2187400586a4121c54c53b85\n```\n\nTo use your hexadecimal string with this plugin you would need to convert it back into a Buffer:\n\n```js\nfastify.register(require('@fastify/secure-session'), {\n  key: Buffer.from(process.env.COOKIE_KEY, 'hex')\n})\n```\n\nNote: `key` must be a secret key of length [crypto_secretbox_KEYBYTES](https://sodium-friends.github.io/docs/docs/secretkeyboxencryption).\n\n### Clearing the session\n\nYou can call `regenerate` on the session to clear all data. You can also pass an array of keys to keep in the session:\n\n```js\nfastify.post('/clear-session', (request, reply) =\u003e {\n  request.session.regenerate(['user']) //clear all session data except `user` key\n  request.session.regenerate() //clear all session data\n  reply.send('session cleared')\n})\n```\n\n#### Security\n\n- Although the example reads the key from a file on disk, it is poor practice when it comes to security. Ideally, you should store secret/keys in a key management service like Vault, KMS, or something similar and read them at run-time.\n- Use `httpOnly` session cookie for all production purposes to reduce the risk of session highjacking or XSS.\n\n## Using a secret\n\nIt is possible to generate a high-entropy key from a (low-entropy)\nsecret passphrase. This approach is the simplest to use, but it adds\na significant startup delay as strong cryptography is applied.\n\n```js\nconst fastify = require('fastify')({ logger: false })\n\nfastify.register(require('@fastify/secure-session'), {\n  secret: 'averylogphrasebiggerthanthirtytwochars',\n  salt: 'mq9hDxBVDbspDR6n',\n  cookie: {\n    path: '/',\n    httpOnly: true // Use httpOnly for all production purposes\n    // options for setCookie, see https://github.com/fastify/fastify-cookie\n  }\n})\n\nfastify.post('/', (request, reply) =\u003e {\n  request.session.set('data', request.body)\n  reply.send('session set')\n})\n\nfastify.get('/', (request, reply) =\u003e {\n  const data = request.session.get('data')\n  if (!data) {\n    reply.code(404).send()\n    return\n  }\n  reply.send(data)\n})\n\nfastify.get('/all', (request, reply) =\u003e {\n  // get all data from session\n  const data = request.session.data()\n  if (!data) {\n    reply.code(404).send()\n    return\n  }\n  reply.send(data)\n})\n\nfastify.listen({ port: 3000 })\n```\n\n## Using Keys with key rotation\n\nIt is possible to use a non-empty array for the key field to support key rotation as an additional security measure.\nCookies will always be signed with the first key in the array to try to \"err on the side of performance\" however\nif decoding the key fails, it will attempt to decode using every subsequent value in the key array.\n\nIMPORTANT: The new key you are trying to rotate to should always be the first key in the array. For example:\n\n```js\n// first time running the app\nfastify.register(require('@fastify/secure-session'), {\n  key: [mySecureKey],\n\n  cookie: {\n    path: '/'\n    // options for setCookie, see https://github.com/fastify/fastify-cookie\n  }\n})\n```\n\nThe above example will sign and encrypt/decrypt sessions just fine. However, what if you want an extra security measure of\nbeing able to rotate your secret credentials for your application? This library supports this by allowing you to\ndo the following:\n\n```js\n// first time running the app\nfastify.register(require('@fastify/secure-session'), {\n  key: [myNewKey, mySecureKey],\n\n  cookie: {\n    path: '/'\n    // options for setCookie, see https://github.com/fastify/fastify-cookie\n  }\n})\n```\n\nSee that `myNewKey` was added to the first index position in the key array. This allows any sessions that were created\nwith the original `mySecureKey` to still be decoded. The first time a session signed with an older key is \"seen\", by the application, this library will re-sign the cookie with the newest session key therefore improving performance for any subsequent session decodes.\n\nTo see a full working example, make sure you generate `secret-key1` and `secret-key2` alongside the js file below by running:\n\n```\nnpx @fastify/secure-session \u003e secret-key1\nnpx @fastify/secure-session \u003e secret-key2\n```\n\n```js\nconst fs = require('node:fs')\nconst fastify = require('fastify')({ logger: false })\n\nconst key1 = fs.readFileSync(path.join(__dirname, 'secret-key1'))\nconst key2 = fs.readFileSync(path.join(__dirname, 'secret-key2'))\n\nfastify.register(require('@fastify/secure-session'), {\n  // any old sessions signed with key2 will still be decoded successfully the first time and\n  // then re-signed with key1 to keep good performance with subsequent calls\n  key: [key1, key2],\n\n  cookie: {\n    path: '/'\n    // options for setCookie, see https://github.com/fastify/fastify-cookie\n  }\n})\n\nfastify.post('/', (request, reply) =\u003e {\n  // will always be encrypted using `key1` with the configuration above\n  request.session.set('data', request.body)\n  reply.send('session set')\n})\n\nfastify.get('/', (request, reply) =\u003e {\n  // will attempt to decode using key1 and then key2 if decoding with key1 fails\n  const data = request.session.get('data')\n  if (!data) {\n    reply.code(404).send()\n    return\n  }\n  reply.send(data)\n})\n\nfastify.listen({ port: 3000 })\n```\n\nWARNING: The more keys you have in the key array can make the decode operation get expensive if too many keys are used\nat once. It is recommended to only use 2 keys at a given time so that the most decode attempts will ever be is 2.\nThis should allow ample support time for supporting sessions with an old key while rotating to the new one. If you have\nlong-lived sessions it could be possible to need to support 3 or even 4 keys. Since old sessions are re-signed\nwith the key at the first index the next time they are seen by the application, you can get away with this. That first\ntime the older session is decoded will be a little more expensive though.\n\nFor a full \"start to finish\" example without having to generate keys and setup a server file, see the _second_ test case in the test file at `/test/key-rotation.js` in this repo.\n\n## Configuring cookie options inside a route\n\nYou can configure the options for `setCookie` inside a route by using the `session.options()` method.\n\n```js\nfastify.post('/', (request, reply) =\u003e {\n  request.session.set('data', request.body)\n  // .options takes any parameter that you can pass to setCookie\n  request.session.options({ maxAge: 60 * 60 }); // 3600 seconds =\u003e maxAge is always passed in seconds\n  reply.send('hello world')\n})\n```\n\n## Integrating with other libraries\n\nIf you need to encode or decode a session in related systems (like say `@fastify/websocket`, which does not use normal Fastify `Request` objects), you can use `@fastify/secure-session`'s decorators to encode and decode sessions yourself. This is less than ideal as this library's cookie setting code is battle-tested by the community, but the option is there if you need it.\n\n```js\nfastify.createSecureSession({ foo: 'bar' })\n// =\u003e Session returns a session object for manipulating with .get and .set to then be encoded with encodeSecureSession\n\nfastify.encodeSecureSession(request.session)\n// =\u003e \"abcdefg\" returns the signed and encrypted cookie string, suitable for passing to a Set-Cookie header\n\nfastify.decodeSecureSession(request.cookies['session'])\n// =\u003e Session | null  returns a session object which you can use to .get values from if decoding is successful, and null otherwise\n```\n\nWhen using multiple sessions, you will have to provide the sessionName when encoding and decoding the session.\n\n```js\nfastify.encodeSecureSession(request.session, 'mySecondSession')\n\nfastify.decodeSecureSession(request.cookies['session'], undefined, 'mySecondSession')\n```\n\n## Add TypeScript types\n\nThe session data is defined as an interface called `SessionData`. It can be extended with [declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) for improved type support.\n\n```ts\ndeclare module '@fastify/secure-session' {\n  interface SessionData {\n    foo: string;\n  }\n}\n\nfastify.get('/', (request, reply) =\u003e {\n  request.session.get('foo'); // typed `string | undefined`\n  reply.send('hello world')\n})\n```\n\nWhen using a custom sessionName or using multiple sessions the types should be configured as follows:\n\n```ts\ninterface FooSessionData {\n  foo: string;\n}\n\ndeclare module \"fastify\" {\n  interface FastifyRequest {\n    foo: Session\u003cFooSessionData\u003e;\n  }\n}\n\nfastify.get('/', (request, reply) =\u003e {\n  request.foo.get('foo'); // typed `string | undefined`\n  reply.send('hello world')\n})\n```\n\n## Security Notice\n\n`@fastify/secure-session` stores the session within a cookie, and as a result an attacker could impersonate a user\nif the cookie is leaked. The maximum expiration time of the session is set by the `expiry` option, which has default\n1 day. Adjust this parameter accordingly.\nMoreover, to protect users from further attacks, all cookies are created as \"http only\" if not specified otherwise.\n\n## License\n\nLicensed under [MIT](./LICENSE).\n","funding_links":["https://github.com/sponsors/fastify","https://opencollective.com/fastify"],"categories":["JavaScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffastify%2Ffastify-secure-session","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffastify%2Ffastify-secure-session","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffastify%2Ffastify-secure-session/lists"}