Gathering detailed insights and metrics for @fastify/secure-session
Gathering detailed insights and metrics for @fastify/secure-session
Gathering detailed insights and metrics for @fastify/secure-session
Gathering detailed insights and metrics for @fastify/secure-session
npm install @fastify/secure-session
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
210 Stars
271 Commits
48 Forks
20 Watching
2 Branches
53 Contributors
Updated on 22 Nov 2024
Minified
Minified + Gzipped
JavaScript (96.93%)
TypeScript (3.07%)
Cumulative downloads
Total Downloads
Last day
17.9%
4,648
Compared to previous day
Last week
0.8%
22,460
Compared to previous week
Last month
8.3%
99,759
Compared to previous month
Last year
75.8%
1,466,925
Compared to previous year
3
Create a secure stateless cookie session for Fastify, based on libsodium's Secret Key Box Encryption and @fastify/cookie.
First generate a key with:
1npx @fastify/secure-session > secret-key
If running in Windows Powershell, you should use this command instead:
1npx @fastify/secure-session | Out-File -Encoding default -NoNewline -FilePath secret-key
If you have not previously used this module with npx, you will be prompted to install it, which with the output redirect will cause the command to wait forever for input.
To avoid this use the --yes
flag with npx:
1npx --yes @fastify/secure-session > secret-key
If 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:
1./node_modules/@fastify/secure-session/genkey.js > secret-key
Then, register the plugin as follows:
1'use strict' 2 3const fastify = require('fastify')({ logger: false }) 4const fs = require('node:fs') 5const path = require('node:path') 6 7fastify.register(require('@fastify/secure-session'), { 8 // the name of the attribute decorated on the request-object, defaults to 'session' 9 sessionName: 'session', 10 // the name of the session cookie, defaults to value of sessionName 11 cookieName: 'my-session-cookie', 12 // adapt this to point to the directory where secret-key is located 13 key: fs.readFileSync(path.join(__dirname, 'secret-key')), 14 // the amount of time the session is considered valid; this is different from the cookie options 15 // and based on value wihin the session. 16 expiry: 24 * 60 * 60, // Default 1 day 17 cookie: { 18 path: '/' 19 // options for setCookie, see https://github.com/fastify/fastify-cookie 20 } 21}) 22 23fastify.post('/', (request, reply) => { 24 request.session.set('data', request.body) 25 26 // or when using a custom sessionName: 27 request.customSessionName.set('data', request.body) 28 29 reply.send('hello world') 30}) 31 32fastify.get('/', (request, reply) => { 33 const data = request.session.get('data') 34 if (!data) { 35 reply.code(404).send() 36 return 37 } 38 reply.send(data) 39}) 40 41fastify.get('/ping', (request, reply) => { 42 request.session.options({maxAge: 3600}) 43 44 // Send the session cookie to the client even if the session data didn't change 45 // can be used to update cookie expiration 46 request.session.touch() 47 reply.send('pong') 48}) 49 50fastify.post('/logout', (request, reply) => { 51 request.session.delete() 52 reply.send('logged out') 53})
If you enable debug
level logging,
you will see what steps the library is doing and understand why a session you
expect to be there is not present. For extra details, you can also enable trace
level logging.
Note: 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)
If 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.
1fastify.register(require('@fastify/secure-session'), [{ 2 sessionName: 'mySession', 3 cookieName: 'my-session-cookie', 4 key: fs.readFileSync(path.join(__dirname, 'secret-key')), 5 cookie: { 6 path: '/' 7 } 8}, { 9 sessionName: 'myOtherSession', 10 key: fs.readFileSync(path.join(__dirname, 'another-secret-key')), 11 cookie: { 12 path: '/path', 13 maxAge: 100 14 } 15}]) 16 17fastify.post('/', (request, reply) => { 18 request.mySession.set('data', request.body) 19 request.myOtherSession.set('data', request.body) 20 reply.send('hello world') 21})
You 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.
To convert a key file into a hexadecimal string you can do this in an npm script:
1const keyBuffer = fs.readFileSync(path.join(__dirname, 'secret-key')); 2const hexString = keyBuffer.toString('hex'); 3console.log(hexString) // Outputs: 4fe91796c30bd989d95b62dc46c7c3ba0b6aa2df2187400586a4121c54c53b85
To use your hexadecimal string with this plugin you would need convert it back into a Buffer:
1fastify.register(require('@fastify/secure-session'), { 2 key: Buffer.from(process.env.COOKIE_KEY, 'hex') 3})
Note: key
must be a secret key of length crypto_secretbox_KEYBYTES.
You can call regenerate
on the session to clear all data. You can also pass an array of keys to keep in the session:
1fastify.post('/clear-session', (request, reply) => { 2 request.session.regenerate(['user']) //clear all session data except `user` key 3 request.session.regenerate() //clear all session data 4 reply.send('session cleared') 5})
httpOnly
session cookie for all production purposes to reduce the risk of session highjacking or XSS.It is possible to generate a high-entropy key from a (low-entropy) secret passphrase. This approach is the simplest to use, but it adds a significant startup delay as strong cryptography is applied.
1const fastify = require('fastify')({ logger: false }) 2 3fastify.register(require('@fastify/secure-session'), { 4 secret: 'averylogphrasebiggerthanthirtytwochars', 5 salt: 'mq9hDxBVDbspDR6n', 6 cookie: { 7 path: '/', 8 httpOnly: true // Use httpOnly for all production purposes 9 // options for setCookie, see https://github.com/fastify/fastify-cookie 10 } 11}) 12 13fastify.post('/', (request, reply) => { 14 request.session.set('data', request.body) 15 reply.send('session set') 16}) 17 18fastify.get('/', (request, reply) => { 19 const data = request.session.get('data') 20 if (!data) { 21 reply.code(404).send() 22 return 23 } 24 reply.send(data) 25}) 26 27fastify.get('/all', (request, reply) => { 28 // get all data from session 29 const data = request.session.data() 30 if (!data) { 31 reply.code(404).send() 32 return 33 } 34 reply.send(data) 35}) 36 37fastify.listen({ port: 3000 })
It is possible to use an non-empty array for the key field to support key rotation as an additional security measure. Cookies will always be signed with the first key in the array to try to "err on the side of performance" however if decoding the key fails, it will attempt to decode using every subsequent value in the key array.
IMPORTANT: The new key you are trying to rotate to should always be the first key in the array. For example:
1// first time running the app 2fastify.register(require('@fastify/secure-session'), { 3 key: [mySecureKey], 4 5 cookie: { 6 path: '/' 7 // options for setCookie, see https://github.com/fastify/fastify-cookie 8 } 9})
The above example will sign and encrypt/decrypt sessions just fine. However, what if you want an extra security measure of being able to rotate your secret credentials for your application? This library supports this by allowing you to do the following:
1// first time running the app 2fastify.register(require('@fastify/secure-session'), { 3 key: [myNewKey, mySecureKey], 4 5 cookie: { 6 path: '/' 7 // options for setCookie, see https://github.com/fastify/fastify-cookie 8 } 9})
See that myNewKey
was added to the first index position in the key array. This allows any sessions that were created
with 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.
To see a full working example, make sure you generate secret-key1
and secret-key2
alongside the js file below by running:
npx @fastify/secure-session > secret-key1
npx @fastify/secure-session > secret-key2
1const fs = require('node:fs') 2const fastify = require('fastify')({ logger: false }) 3 4const key1 = fs.readFileSync(path.join(__dirname, 'secret-key1')) 5const key2 = fs.readFileSync(path.join(__dirname, 'secret-key2')) 6 7fastify.register(require('@fastify/secure-session'), { 8 // any old sessions signed with key2 will still be decoded successfully the first time and 9 // then re-signed with key1 to keep good performance with subsequent calls 10 key: [key1, key2], 11 12 cookie: { 13 path: '/' 14 // options for setCookie, see https://github.com/fastify/fastify-cookie 15 } 16}) 17 18fastify.post('/', (request, reply) => { 19 // will always be encrypted using `key1` with the configuration above 20 request.session.set('data', request.body) 21 reply.send('session set') 22}) 23 24fastify.get('/', (request, reply) => { 25 // will attempt to decode using key1 and then key2 if decoding with key1 fails 26 const data = request.session.get('data') 27 if (!data) { 28 reply.code(404).send() 29 return 30 } 31 reply.send(data) 32}) 33 34fastify.listen({ port: 3000 })
WARNING: The more keys you have in the key array can make the decode operation get expensive if too many keys are used. at once. It is recommended to only use 2 keys at a given time so that the most decode attempts will ever be is 2. This should allow ample support time for supporting sessions with an old key while rotating to the new one. If you have really long lived sessions it could be possible to need to support 3 or even 4 keys. Since old sessions are re-signed with the key at the first index the next time they are seen by the application, you can get away with this. That first time the older session is decoded will be a little more expensive though.
For 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.
You can configure the options for setCookie
inside a route by using the session.options()
method.
1fastify.post('/', (request, reply) => { 2 request.session.set('data', request.body) 3 // .options takes any parameter that you can pass to setCookie 4 request.session.options({ maxAge: 60 * 60 }); // 3600 seconds => maxAge is always passed in seconds 5 reply.send('hello world') 6})
If 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.
1fastify.createSecureSession({ foo: 'bar' }) 2// => Session returns a session object for manipulating with .get and .set to then be encoded with encodeSecureSession 3 4fastify.encodeSecureSession(request.session) 5// => "abcdefg" returns the signed and encrypted cookie string, suitable for passing to a Set-Cookie header 6 7fastify.decodeSecureSession(request.cookies['session']) 8// => Session | null returns a session object which you can use to .get values from if decoding is successful, and null otherwise
When using multiple sessions, you will have to provide the sessionName when encoding and decoding the session.
1fastify.encodeSecureSession(request.session, 'mySecondSession')
2
3fastify.decodeSecureSession(request.cookies['session'], undefined, 'mySecondSession')
The session data is defined as an interface called SessionData
. It can be extended with declaration merging for improved type support.
1declare module '@fastify/secure-session' { 2 interface SessionData { 3 foo: string; 4 } 5} 6 7fastify.get('/', (request, reply) => { 8 request.session.get('foo'); // typed `string | undefined` 9 reply.send('hello world') 10})
When using a custom sessionName or using multiple sessions the types should be configured as follows:
1interface FooSessionData { 2 foo: string; 3} 4 5declare module "fastify" { 6 interface FastifyRequest { 7 foo: Session<FooSessionData>; 8 } 9} 10 11fastify.get('/', (request, reply) => { 12 request.foo.get('foo'); // typed `string | undefined` 13 reply.send('hello world') 14})
@fastify/secure-session
stores the session within a cookie, and as a result an attacker could impersonate a user
if the cookie is leaked. The maximum expiration time of the session is set by the expiry
option, which has default
1 day. Adjust this parameter accordingly.
Moreover, to protect users from further attacks, all cookies are created as "http only" if not specified otherwise.
MIT
The latest stable version of the package.
Stable Version
1
7.4/10
Summary
@fastify/secure-session: Reuse of destroyed secure session cookie
Affected Versions
< 7.3.0
Patched Versions
7.3.0
Reason
no dangerous workflow patterns detected
Reason
15 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 10
Reason
no binaries found in the repo
Reason
0 existing vulnerabilities detected
Reason
license file detected
Details
Reason
security policy file detected
Details
Reason
SAST tool is not run on all commits -- score normalized to 6
Details
Reason
Found 11/23 approved changesets -- score normalized to 4
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
project is not fuzzed
Details
Reason
branch protection not enabled on development/release branches
Details
Score
Last Scanned on 2024-11-25
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More