Skip to content

Commit

Permalink
feat: LRU cache key (#504)
Browse files Browse the repository at this point in the history
* feat: token hasher option

* chore: verifier

* chore: renamed parameter

* Update README.md

Co-authored-by: Simone Busoli <[email protected]>

* chore: performance of cacheKeyBuilder

---------

Co-authored-by: Simone Busoli <[email protected]>
  • Loading branch information
ilteoood and simoneb authored Nov 20, 2024
1 parent b1d56ae commit 79f367c
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 9 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ Create a verifier function by calling `createVerifier` and providing one or more

- `clockTolerance`: Timespan in milliseconds is the tolerance to apply to the current timestamp when performing time comparisons. Default is `0`.

- `cacheKeyBuilder`: The function that will be used to create the [cache's key](#caching) for each token. To mitigate the risk of leaking sensitive information and generate collisions, [a hashing function](./src/utils.js) is used by default.

The verifier is a function which accepts a token (as Buffer or string) and returns the payload or the sections of the token.

If the `key` option is a function, the signer will also accept a Node style callback and will return a promise, supporting therefore both callback and async/await styles.
Expand Down Expand Up @@ -274,6 +276,10 @@ For verified tokens, caching considers the time sensitive claims of the token (`
Performances improvements varies by uses cases and by the type of the operation performed and the algorithm used.
The default `cacheKeyBuilder` is a function that hashes the token. This provides a good level of protection against sensitive information leaks, but it also has a significant performance impact (almost 10x slower, as it's a CPU bound operation). If you are using caching and you are not concerned about potential information leaks you can use the identity function as `cacheKeyBuilder` to improve them.
For a detailed discussion about it, take a look at [this issue](https://github.com/nearform/fast-jwt/issues/503).
> **_Note:_** Errors are not cached by default, to change this behaviour use the `errorCacheTTL` option.
## Token Error Codes
Expand Down
23 changes: 14 additions & 9 deletions src/verifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ function cacheSet(
maxAge,
clockTimestamp = Date.now(),
clockTolerance,
errorCacheTTL
errorCacheTTL,
cacheKeyBuilder
},
value
) {
Expand All @@ -81,7 +82,7 @@ function cacheSet(
if (value instanceof TokenError) {
const ttl = typeof errorCacheTTL === 'function' ? errorCacheTTL(value) : errorCacheTTL
cacheValue[2] = clockTimestamp + clockTolerance + ttl
cache.set(hashToken(token), cacheValue)
cache.set(cacheKeyBuilder(token), cacheValue)
return value
}

Expand All @@ -104,7 +105,7 @@ function cacheSet(
const maxTTL = clockTimestamp + clockTolerance + cacheTTL
cacheValue[2] = cacheValue[2] === 0 ? maxTTL : Math.min(cacheValue[2], maxTTL)

cache.set(hashToken(token), cacheValue)
cache.set(cacheKeyBuilder(token), cacheValue)

return value
}
Expand Down Expand Up @@ -241,7 +242,8 @@ function verify(
decode,
cache,
requiredClaims,
errorCacheTTL
errorCacheTTL,
cacheKeyBuilder
},
token,
cb
Expand All @@ -250,7 +252,7 @@ function verify(

// Check the cache
if (cache) {
const [value, min, max] = cache.get(hashToken(token)) || [undefined, 0, 0]
const [value, min, max] = cache.get(cacheKeyBuilder(token)) || [undefined, 0, 0]
const now = clockTimestamp || Date.now()

// Validate time range
Expand Down Expand Up @@ -294,7 +296,8 @@ function verify(
maxAge,
clockTimestamp,
clockTolerance,
payload
payload,
cacheKeyBuilder
}
const validationContext = { validators, allowedAlgorithms, checkTyp, clockTimestamp, clockTolerance, requiredClaims }

Expand Down Expand Up @@ -373,8 +376,9 @@ module.exports = function createVerifier(options) {
allowedIss,
allowedSub,
allowedNonce,
requiredClaims
} = { cacheTTL: 600_000, clockTolerance: 0, errorCacheTTL: -1, ...options }
requiredClaims,
cacheKeyBuilder
} = { cacheTTL: 600_000, clockTolerance: 0, errorCacheTTL: -1, cacheKeyBuilder: hashToken, ...options }

// Validate options
if (!Array.isArray(allowedAlgorithms)) {
Expand Down Expand Up @@ -494,7 +498,8 @@ module.exports = function createVerifier(options) {
validators,
decode: createDecoder({ complete: true }),
cache: createCache(cacheSize),
requiredClaims
requiredClaims,
cacheKeyBuilder
}

// Return the verifier
Expand Down
21 changes: 21 additions & 0 deletions test/verifier.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,27 @@ test('caching - sync', t => {
t.assert.ok(verifier.cache.get(hashToken(invalidToken))[0] instanceof TokenError)
})

test('caching - sync - custom cacheKeyBuilder', t => {
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxfQ.57TF7smP9XDhIexBqPC-F1toZReYZLWb_YRU5tv0sxM'
const invalidToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxfQ.aaa'

const verifier = createVerifier({ key: 'secret', cache: true, cacheKeyBuilder: (id) => id })

t.assert.equal(verifier.cache.size, 0)
t.assert.deepStrictEqual(verifier(token), { a: 1 })
t.assert.equal(verifier.cache.size, 1)
t.assert.deepStrictEqual(verifier(token), { a: 1 })
t.assert.equal(verifier.cache.size, 1)

t.assert.throws(() => verifier(invalidToken), { message: 'The token signature is invalid.' })
t.assert.equal(verifier.cache.size, 2)
t.assert.throws(() => verifier(invalidToken), { message: 'The token signature is invalid.' })
t.assert.equal(verifier.cache.size, 2)

t.assert.deepStrictEqual(verifier.cache.get(token)[0], { a: 1 })
t.assert.ok(verifier.cache.get(invalidToken)[0] instanceof TokenError)
})

test('caching - async', async t => {
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxfQ.57TF7smP9XDhIexBqPC-F1toZReYZLWb_YRU5tv0sxM'
const invalidToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxfQ.aaa'
Expand Down

0 comments on commit 79f367c

Please sign in to comment.