Giter Club home page Giter Club logo

fast-jwt's Introduction

fast-jwt

Package Version CI

Fast JSON Web Token implementation.

Installation

Just run:

npm install fast-jwt

Usage

createSigner

Create a signer function by calling createSigner and providing one or more of the following options:

  • key: A string or a buffer containing the secret for HS* algorithms or the PEM encoded private key for RS*, PS*, ES* and EdDSA algorithms. If the key is a passphrase protected private key it must be an object (more details below). The key can also be a function accepting a Node style callback or a function returning a promise. This is the only mandatory option, which MUST NOT be provided if the token algorithm is none.

  • algorithm: The algorithm to use to sign the token. The default value is autodetected from the key, using RS256 for RSA private keys, HS256 for plain secrets and the corresponding ES or EdDSA algorithms for EC or Ed* private keys.

  • mutatePayload: If set to true, the original payload will be modified in place (via Object.assign) by the signing function. This is useful if you need a raw reference to the payload after claims have been applied to it but before it has been encoded into a token. Default is false.

  • expiresIn: Time span (in milliseconds or text describing time) after which the token expires, added as the exp claim in the payload as defined by the section 4.1.4 of RFC 7519. This will override any existing value in the claim.

    Eg: 60, "2 days", "10h", "7d". A numeric value is interpreted as a seconds count. If you use a string be sure you provide the time units (days, hours, etc), otherwise milliseconds unit is used by default ("120" is equal to "120ms"). For more info look into @lukeed/ms.

  • notBefore: Time span (in milliseconds or text describing time) before the token is active, added as the nbf claim in the payload as defined by the section 4.1.5 of RFC 7519. This will override any existing value in the claim.

    Eg: 60, "2 days", "10h", "7d". A numeric value is interpreted as a seconds count. If you use a string be sure you provide the time units (days, hours, etc), otherwise milliseconds unit is used by default ("120" is equal to "120ms"). For more info look into @lukeed/ms.

  • jti: The token unique identifier, added as the jti claim in the payload as defined by the section 4.1.7 of RFC 7519. This will override any existing value in the claim.

  • aud: The token audience, added as the aud claim in the payload as defined by the section 4.1.3 of RFC 7519. This claim identifies the recipients that the token is intended for. It must be a string or an array of strings. This will override any existing value in the claim.

  • iss: The token issuer, added as the iss claim in the payload as defined by the section 4.1.1 of RFC 7519. It must be a string. This will override any existing value in the claim.

  • sub: The token subject, added as the sub claim in the payload as defined by the section 4.1.2 of RFC 7519. It must be a string. This will override any existing value in the claim.

  • nonce: The token nonce, added as the nonce claim in the payload. The nonce value is used to associate a Client session with an ID Token. Note that this is a IANA JSON Web Token Claims Registry public claim registered by OpenID Connect (OIDC). It must be a string. This will override any existing value in the claim.

  • kid: The token key id, added as the kid claim in the header section (see section 4.1.4 of RFC 7515 and section 4.5 of RFC 7517). It must be a string.

  • header: Additional claims to add to the header section. This will override the typ and kid claims.

  • noTimestamp: If set to true, the iat claim should not be added to the token. Default is false.

  • clockTimestamp: The timestamp in milliseconds (like the output of Date.now()) that should be used as the current time for all necessary time comparisons. Default is the system time.

The signer is a function which accepts a payload and returns the token.

The payload must be an object.

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.

If the key is a passphrase protected private key, the algorithm option must be provided and must be either a RS* or ES* encoded key and the key option must be an object with the following structure:

{
  key: '<YOUR_RSA_ENCRYPTED_PRIVATE_KEY>',
  passphrase: '<PASSPHRASE_THAT_WAS_USED_TO_ENCRYPT_THE_PRIVATE_KEY>'
}

Example

const { createSigner } = require('fast-jwt')

// Sync style
const signSync = createSigner({ key: 'secret' })
const token = signSync({ a: 1, b: 2, c: 3 })
// => eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJiIjoyLCJjIjozLCJpYXQiOjE1Nzk1MjEyMTJ9.mIcxteEVjbh2MnKQ3EQlojZojGSyA_guqRBYHQURcfnCSSBTT2OShF8lo9_ogjAv-5oECgmCur_cDWB7x3X53g

// Callback style
const signWithCallback = createSigner({ key: (callback) => callback(null, 'secret') })

signWithCallback({ a: 1, b: 2, c: 3 }, (err, token) => {
  // token === eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJiIjoyLCJjIjozLCJpYXQiOjE1Nzk1MjEyMTJ9.mIcxteEVjbh2MnKQ3EQlojZojGSyA_guqRBYHQURcfnCSSBTT2OShF8lo9_ogjAv-5oECgmCur_cDWB7x3X53g
})

// Promise style - Note that the key function style and the signer function style are unrelated
async function test() {
  const signWithPromise = createSigner({ key: async () => 'secret' })

  const token = await signWithPromise({ a: 1, b: 2, c: 3 })
  // => eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJiIjoyLCJjIjozLCJpYXQiOjE1Nzk1MjEyMTJ9.mIcxteEVjbh2MnKQ3EQlojZojGSyA_guqRBYHQURcfnCSSBTT2OShF8lo9_ogjAv-5oECgmCur_cDWB7x3X53g
}

// Using password protected private key - in this case you MUST provide the algorithm as well
const signSync = createSigner({
  algorithm: '<ANY_RS*_OR_ES*_ALGORITHM>',
  key: {
    key: '<YOUR_RSA_ENCRYPTED_PRIVATE_KEY>',
    passphrase: '<PASSPHRASE_THAT_WAS_USED_TO_ENCRYPT_THE_PRIVATE_KEY>'
  })
const token = signSync({ a: 1, b: 2, c: 3 })

createDecoder

Create a decoder function by calling createDecoder and providing one or more of the following options:

  • complete: Return an object with the decoded header, payload, signature and input (the token part before the signature), instead of just the content of the payload. Default is false.

  • checkTyp: When validating the decoded header, setting this option forces the check of the typ property against this value. Example: checkTyp: 'JWT'. Default is undefined.

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

Examples

const { createDecoder } = require('fast-jwt')
const token =
  'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJiIjoyLCJjIjozLCJpYXQiOjE1Nzk1MjEyMTJ9.mIcxteEVjbh2MnKQ3EQlojZojGSyA_guqRBYHQURcfnCSSBTT2OShF8lo9_ogjAv-5oECgmCur_cDWB7x3X53g'

// Standard decoder
const decode = createDecoder()
const payload = decode(token)
// => { a: 1, b: 2, c: 3, iat: 1579521212 }

// Complete decoder
const decodeComplete = createDecoder({ complete: true })
const sections = decodeComplete(token)
/* => 
  { 
    header: { alg: 'HS512', typ: 'JWT' }, 
    payload: { a: 1, b: 2, c: 3, iat: 1579521212 },
    signature: 'mIcxteEVjbh2MnKQ3EQlojZojGSyA/guqRBYHQURcfnCSSBTT2OShF8lo9/ogjAv+5oECgmCur/cDWB7x3X53g==',
    input: 'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJiIjoyLCJjIjozLCJpYXQiOjE1Nzk1MjEyMTJ9' 
  }
*/

createVerifier

Create a verifier function by calling createVerifier and providing one or more of the following options:

  • key: A string or a buffer containing the secret for HS* algorithms or the PEM encoded public key for RS*, PS*, ES* and EdDSA algorithms. The key can also be a function accepting a Node style callback or a function returning a promise. This is the only mandatory option, which MUST NOT be provided if the token algorithm is none.

  • algorithms: List of strings with the names of the allowed algorithms. By default, all algorithms are accepted.

  • complete: Return an object with the decoded header, payload, signature and input (the token part before the signature), instead of just the content of the payload. Default is false.

  • cache: A positive number specifying the size of the verified tokens cache (using LRU strategy). Setting to true is equivalent to provide the size 1000. When enabled, as you can see in the benchmarks section below, performances dramatically improve. By default the cache is disabled.

  • cacheTTL: The maximum time to live of a cache entry (in milliseconds). If the token has a earlier expiration or the verifier has a shorter maxAge, the earlier takes precedence. The default is 600000, which is 10 minutes.

  • errorCacheTTL: A number or function function (tokenError) => number that represents the maximum time to live of a cache error entry (in milliseconds). Example: the key function fails or does not return a secret or public key. By default errors are not cached, the errorCacheTTL default value is -1.

  • allowedJti: A string, a regular expression, an array of strings or an array of regular expressions containing allowed values for the id claim (jti). By default, all values are accepted.

  • allowedAud: A string, a regular expression, an array of strings or an array of regular expressions containing allowed values for the audience claim (aud). By default, all values are accepted.

  • allowedIss: A string, a regular expression, an array of strings or an array of regular expressions containing allowed values for the issuer claim (iss). By default, all values are accepted.

  • allowedSub: A string, a regular expression, an array of strings or an array of regular expressions containing allowed values for the subject claim (sub). By default, all values are accepted.

  • allowedNonce: A string, a regular expression, an array of strings or an array of regular expressions containing allowed values for the nonce claim (nonce). By default, all values are accepted.

  • requiredClaims: An array of strings containing which claims should exist in the token. By default, no claim is marked as required.

  • ignoreExpiration: Do not validate the expiration of the token. Default is false.

  • ignoreNotBefore: Do not validate the activation of the token. Default is false.

  • maxAge: The maximum allowed age (in milliseconds) for tokens to still be valid. By default this is not checked.

  • clockTimestamp: The timestamp in milliseconds (like the output of Date.now()) that should be used as the current time for all necessary time comparisons. Default is the system time.

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

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.

Examples

const { createVerifier, TOKEN_ERROR_CODES } = require('fast-jwt')
const token =
  'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJiIjoyLCJjIjozLCJpYXQiOjE1Nzk1MjEyMTJ9.mIcxteEVjbh2MnKQ3EQlojZojGSyA_guqRBYHQURcfnCSSBTT2OShF8lo9_ogjAv-5oECgmCur_cDWB7x3X53g'

// Sync style
const verifySync = createVerifier({ key: 'secret' })
const payload = verifySync(token)
// => { a: 1, b: 2, c: 3, iat: 1579521212 }

// Callback style with complete return
const verifyWithCallback = createVerifier({ key: callback => callback(null, 'secret'), complete: true })

verifyWithCallback(token, (err, sections) => {
  /*
  sections === {
    header: { alg: 'HS512', typ: 'JWT' },
    payload: { a: 1, b: 2, c: 3, iat: 1579521212 },
    signature: 'mIcxteEVjbh2MnKQ3EQlojZojGSyA/guqRBYHQURcfnCSSBTT2OShF8lo9/ogjAv+5oECgmCur/cDWB7x3X53g==',
    input: 'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJiIjoyLCJjIjozLCJpYXQiOjE1Nzk1MjEyMTJ9'
  }
*/
})

// Promise style - Note that the key function style and the verifier function style are unrelated
async function test() {
  const verifyWithPromise = createVerifier({ key: async () => 'secret' })

  const payload = await verifyWithPromise(token)
  // => { a: 1, b: 2, c: 3, iat: 1579521212 }
}

// custom errorCacheTTL verifier
const verifier = createVerifier({
  key: 'secret',
  cache: true,
  errorCacheTTL: tokenError => {
    // customize the ttl based on the error code
    if (tokenError.code === TOKEN_ERROR_CODES.invalidKey) {
      return 1000
    }
    return 2000
  }
})

Algorithms supported

This is the lisf of currently supported algorithms:

Name Description
none Empty algorithm - The token signature section will be empty
HS256 HMAC using SHA-256 hash algorithm
HS384 HMAC using SHA-384 hash algorithm
HS512 HMAC using SHA-512 hash algorithm
ES256 ECDSA using P-256 curve and SHA-256 hash algorithm
ES384 ECDSA using P-384 curve and SHA-384 hash algorithm
ES512 ECDSA using P-521 curve and SHA-512 hash algorithm
RS256 RSASSA-PKCS1-v1_5 using SHA-256 hash algorithm
RS384 RSASSA-PKCS1-v1_5 using SHA-384 hash algorithm
RS512 RSASSA-PKCS1-v1_5 using SHA-512 hash algorithm
PS256 RSASSA-PSS using SHA-256 hash algorithm
PS384 RSASSA-PSS using SHA-384 hash algorithm
PS512 RSASSA-PSS using SHA-512 hash algorithm
EdDSA EdDSA tokens using Ed25519 or Ed448 keys, only supported on Node.js 12+

Caching

fast-jwt supports caching of verified tokens.

The cache layer, powered by mnemonist, is a LRU cache which dimension is controlled by the user, as described in the options list.

When caching is enabled, verified tokens are always stored in cache. If the verification fails once, the error is cached as well for the time set by errorCacheTTL and the operation is not retried.

For verified tokens, caching considers the time sensitive claims of the token (iat, nbf and exp) and make sure the verification is retried after a token becomes valid or after a token becomes expired.

Performances improvements varies by uses cases and by the type of the operation performed and the algorithm used.

Note: Errors are not cached by default, to change this behaviour use the errorCacheTTL option.

Token Error Codes

Error codes exported by TOKEN_ERROR_CODES.

JWKS

JWKS is supported via get-jwks. Check out the documentation for integration examples.

Benchmarks

Signing

╔══════════════════════════════╤═════════╤═════════════════╤═══════════╤═════════════════════════╗
║ Slower tests                 │ Samples │          Result │ Tolerance │ Difference with slowest ║
╟──────────────────────────────┼─────────┼─────────────────┼───────────┼─────────────────────────╢
║ HS512 - fast-jwt (async)     │   10000 │ 55766.29 op/sec │  ± 2.85 % │                         ║
║ HS512 - jsonwebtoken (async) │   10000 │ 68764.89 op/sec │  ± 1.25 % │ + 23.31 %               ║
║ HS512 - jsonwebtoken (sync)  │   10000 │ 70191.14 op/sec │  ± 1.84 % │ + 25.87 %               ║
║ HS512 - jose (sync)          │   10000 │ 72844.84 op/sec │  ± 1.72 % │ + 30.63 %               ║
╟──────────────────────────────┼─────────┼─────────────────┼───────────┼─────────────────────────╢
║ Fastest test                 │ Samples │          Result │ Tolerance │ Difference with slowest ║
╟──────────────────────────────┼─────────┼─────────────────┼───────────┼─────────────────────────╢
║ HS512 - fast-jwt (sync)      │   10000 │ 97602.16 op/sec │  ± 1.83 % │ + 75.02 %               ║
╚══════════════════════════════╧═════════╧═════════════════╧═══════════╧═════════════════════════╝

╔══════════════════════════════╤═════════╤═══════════════╤═══════════╤═════════════════════════╗
║ Slower tests                 │ Samples │        Result │ Tolerance │ Difference with slowest ║
╟──────────────────────────────┼─────────┼───────────────┼───────────┼─────────────────────────╢
║ ES512 - fast-jwt (async)     │    1000 │ 419.29 op/sec │  ± 0.34 % │                         ║
║ ES512 - jsonwebtoken (async) │    1000 │ 440.53 op/sec │  ± 0.26 % │ + 5.07 %                ║
║ ES512 - jsonwebtoken (sync)  │    1000 │ 445.91 op/sec │  ± 0.16 % │ + 6.35 %                ║
║ ES512 - jose (sync)          │    1000 │ 452.01 op/sec │  ± 0.20 % │ + 7.80 %                ║
╟──────────────────────────────┼─────────┼───────────────┼───────────┼─────────────────────────╢
║ Fastest test                 │ Samples │        Result │ Tolerance │ Difference with slowest ║
╟──────────────────────────────┼─────────┼───────────────┼───────────┼─────────────────────────╢
║ ES512 - fast-jwt (sync)      │    1000 │ 467.54 op/sec │  ± 0.15 % │ + 11.51 %               ║
╚══════════════════════════════╧═════════╧═══════════════╧═══════════╧═════════════════════════╝

╔══════════════════════════════╤═════════╤═══════════════╤═══════════╤═════════════════════════╗
║ Slower tests                 │ Samples │        Result │ Tolerance │ Difference with slowest ║
╟──────────────────────────────┼─────────┼───────────────┼───────────┼─────────────────────────╢
║ RS512 - fast-jwt (async)     │    1000 │ 196.13 op/sec │  ± 0.28 % │                         ║
║ RS512 - jsonwebtoken (async) │    1000 │ 200.15 op/sec │  ± 0.23 % │ + 2.05 %                ║
║ RS512 - jsonwebtoken (sync)  │    1000 │ 203.72 op/sec │  ± 0.18 % │ + 3.87 %                ║
║ RS512 - jose (sync)          │    1000 │ 245.89 op/sec │  ± 0.39 % │ + 25.37 %               ║
╟──────────────────────────────┼─────────┼───────────────┼───────────┼─────────────────────────╢
║ Fastest test                 │ Samples │        Result │ Tolerance │ Difference with slowest ║
╟──────────────────────────────┼─────────┼───────────────┼───────────┼─────────────────────────╢
║ RS512 - fast-jwt (sync)      │    1000 │ 273.31 op/sec │  ± 0.27 % │ + 39.36 %               ║
╚══════════════════════════════╧═════════╧═══════════════╧═══════════╧═════════════════════════╝

╔══════════════════════════════╤═════════╤═══════════════╤═══════════╤═════════════════════════╗
║ Slower tests                 │ Samples │        Result │ Tolerance │ Difference with slowest ║
╟──────────────────────────────┼─────────┼───────────────┼───────────┼─────────────────────────╢
║ PS512 - jsonwebtoken (sync)  │    1000 │ 194.00 op/sec │  ± 0.27 % │                         ║
║ PS512 - jsonwebtoken (async) │    1000 │ 202.08 op/sec │  ± 0.21 % │ + 4.17 %                ║
║ PS512 - fast-jwt (async)     │    1000 │ 203.36 op/sec │  ± 0.19 % │ + 4.82 %                ║
║ PS512 - jose (sync)          │    1000 │ 266.54 op/sec │  ± 0.29 % │ + 37.39 %               ║
╟──────────────────────────────┼─────────┼───────────────┼───────────┼─────────────────────────╢
║ Fastest test                 │ Samples │        Result │ Tolerance │ Difference with slowest ║
╟──────────────────────────────┼─────────┼───────────────┼───────────┼─────────────────────────╢
║ PS512 - fast-jwt (sync)      │    1000 │ 272.11 op/sec │  ± 0.24 % │ + 40.26 %               ║
╚══════════════════════════════╧═════════╧═══════════════╧═══════════╧═════════════════════════╝

╔══════════════════════════╤═════════╤═════════════════╤═══════════╤═════════════════════════╗
║ Slower tests             │ Samples │          Result │ Tolerance │ Difference with slowest ║
╟──────────────────────────┼─────────┼─────────────────┼───────────┼─────────────────────────╢
║ EdDSA - fast-jwt (async) │    1000 │  8301.50 op/sec │  ± 0.70 % │                         ║
║ EdDSA - jose (sync)      │    1500 │ 16561.83 op/sec │  ± 0.88 % │ + 99.50 %               ║
╟──────────────────────────┼─────────┼─────────────────┼───────────┼─────────────────────────╢
║ Fastest test             │ Samples │          Result │ Tolerance │ Difference with slowest ║
╟──────────────────────────┼─────────┼─────────────────┼───────────┼─────────────────────────╢
║ EdDSA - fast-jwt (sync)  │    3000 │ 17514.99 op/sec │  ± 0.94 % │ + 110.99 %              ║
╚══════════════════════════╧═════════╧═════════════════╧═══════════╧═════════════════════════╝

Decoding

╔═════════════════════════════════╤═════════╤══════════════════╤═══════════╤═════════════════════════╗
║ Slower tests                    │ Samples │           Result │ Tolerance │ Difference with slowest ║
╟─────────────────────────────────┼─────────┼──────────────────┼───────────┼─────────────────────────╢
║ RS512 - jsonwebtoken - complete │   10000 │ 126201.23 op/sec │  ± 2.84 % │                         ║
║ RS512 - jsonwebtoken            │   10000 │ 143571.03 op/sec │  ± 1.82 % │ + 13.76 %               ║
║ RS512 - jose - complete         │   10000 │ 252738.76 op/sec │  ± 5.62 % │ + 100.27 %              ║
║ RS512 - fast-jwt                │   10000 │ 254921.59 op/sec │  ± 3.39 % │ + 102.00 %              ║
║ RS512 - jose                    │   10000 │ 266197.51 op/sec │  ± 4.02 % │ + 110.93 %              ║
╟─────────────────────────────────┼─────────┼──────────────────┼───────────┼─────────────────────────╢
║ Fastest test                    │ Samples │           Result │ Tolerance │ Difference with slowest ║
╟─────────────────────────────────┼─────────┼──────────────────┼───────────┼─────────────────────────╢
║ RS512 - fast-jwt - complete     │   10000 │ 284719.82 op/sec │  ± 3.39 % │ + 125.61 %              ║
╚═════════════════════════════════╧═════════╧══════════════════╧═══════════╧═════════════════════════╝

Note that for decoding the algorithm is irrelevant, so only one was measured.

Verifying

╔═════════════════════════════════════╤═════════╤══════════════════╤═══════════╤═════════════════════════╗
║ Slower tests                        │ Samples │           Result │ Tolerance │ Difference with slowest ║
╟─────────────────────────────────────┼─────────┼──────────────────┼───────────┼─────────────────────────╢
║ HS512 - jsonwebtoken (sync)         │   10000 │  49275.12 op/sec │  ± 1.41 % │                         ║
║ HS512 - fast-jwt (async)            │   10000 │  51353.81 op/sec │  ± 2.98 % │ + 4.22 %                ║
║ HS512 - jsonwebtoken (async)        │   10000 │  51610.98 op/sec │  ± 1.51 % │ + 4.74 %                ║
║ HS512 - jose (sync)                 │   10000 │  64280.92 op/sec │  ± 1.73 % │ + 30.45 %               ║
║ HS512 - fast-jwt (sync)             │   10000 │  75067.57 op/sec │  ± 2.40 % │ + 52.34 %               ║
║ HS512 - fast-jwt (async with cache) │   10000 │ 175013.21 op/sec │  ± 4.42 % │ + 255.18 %              ║
╟─────────────────────────────────────┼─────────┼──────────────────┼───────────┼─────────────────────────╢
║ Fastest test                        │ Samples │           Result │ Tolerance │ Difference with slowest ║
╟─────────────────────────────────────┼─────────┼──────────────────┼───────────┼─────────────────────────╢
║ HS512 - fast-jwt (sync with cache)  │   10000 │ 207199.64 op/sec │  ± 3.15 % │ + 320.50 %              ║
╚═════════════════════════════════════╧═════════╧══════════════════╧═══════════╧═════════════════════════╝

╔═════════════════════════════════════╤═════════╤══════════════════╤═══════════╤═════════════════════════╗
║ Slower tests                        │ Samples │           Result │ Tolerance │ Difference with slowest ║
╟─────────────────────────────────────┼─────────┼──────────────────┼───────────┼─────────────────────────╢
║ ES512 - fast-jwt (async)            │    1000 │    561.01 op/sec │  ± 0.44 % │                         ║
║ ES512 - jsonwebtoken (sync)         │    1000 │    573.52 op/sec │  ± 0.27 % │ + 2.23 %                ║
║ ES512 - jsonwebtoken (async)        │    1000 │    573.74 op/sec │  ± 0.26 % │ + 2.27 %                ║
║ ES512 - fast-jwt (sync)             │    1000 │    597.68 op/sec │  ± 0.30 % │ + 6.54 %                ║
║ ES512 - jose (sync)                 │    1000 │    604.42 op/sec │  ± 0.27 % │ + 7.74 %                ║
║ ES512 - fast-jwt (async with cache) │   10000 │ 189999.48 op/sec │  ± 4.49 % │ + 33767.60 %            ║
╟─────────────────────────────────────┼─────────┼──────────────────┼───────────┼─────────────────────────╢
║ Fastest test                        │ Samples │           Result │ Tolerance │ Difference with slowest ║
╟─────────────────────────────────────┼─────────┼──────────────────┼───────────┼─────────────────────────╢
║ ES512 - fast-jwt (sync with cache)  │   10000 │ 192353.61 op/sec │  ± 4.79 % │ + 34187.22 %            ║
╚═════════════════════════════════════╧═════════╧══════════════════╧═══════════╧═════════════════════════╝

╔═════════════════════════════════════╤═════════╤══════════════════╤═══════════╤═════════════════════════╗
║ Slower tests                        │ Samples │           Result │ Tolerance │ Difference with slowest ║
╟─────────────────────────────────────┼─────────┼──────────────────┼───────────┼─────────────────────────╢
║ RS512 - jsonwebtoken (async)        │    1500 │   7551.10 op/sec │  ± 0.92 % │                         ║
║ RS512 - jsonwebtoken (sync)         │    4500 │   7750.46 op/sec │  ± 0.96 % │ + 2.64 %                ║
║ RS512 - fast-jwt (async)            │    1000 │   8413.41 op/sec │  ± 0.99 % │ + 11.42 %               ║
║ RS512 - jose (sync)                 │    4500 │  12382.58 op/sec │  ± 0.94 % │ + 63.98 %               ║
║ RS512 - fast-jwt (sync)             │    4500 │  12665.45 op/sec │  ± 0.90 % │ + 67.73 %               ║
║ RS512 - fast-jwt (sync with cache)  │   10000 │ 145107.65 op/sec │  ± 7.54 % │ + 1821.68 %             ║
╟─────────────────────────────────────┼─────────┼──────────────────┼───────────┼─────────────────────────╢
║ Fastest test                        │ Samples │           Result │ Tolerance │ Difference with slowest ║
╟─────────────────────────────────────┼─────────┼──────────────────┼───────────┼─────────────────────────╢
║ RS512 - fast-jwt (async with cache) │   10000 │ 158780.83 op/sec │  ± 3.90 % │ + 2002.75 %             ║
╚═════════════════════════════════════╧═════════╧══════════════════╧═══════════╧═════════════════════════╝

╔═════════════════════════════════════╤═════════╤══════════════════╤═══════════╤═════════════════════════╗
║ Slower tests                        │ Samples │           Result │ Tolerance │ Difference with slowest ║
╟─────────────────────────────────────┼─────────┼──────────────────┼───────────┼─────────────────────────╢
║ PS512 - jsonwebtoken (async)        │    2500 │   7240.21 op/sec │  ± 0.89 % │                         ║
║ PS512 - jsonwebtoken (sync)         │    2000 │   7449.38 op/sec │  ± 0.91 % │ + 2.89 %                ║
║ PS512 - fast-jwt (async)            │    1500 │   8301.99 op/sec │  ± 0.81 % │ + 14.67 %               ║
║ PS512 - jose (sync)                 │    4000 │  11944.57 op/sec │  ± 0.99 % │ + 64.98 %               ║
║ PS512 - fast-jwt (sync)             │    1000 │  12881.96 op/sec │  ± 0.76 % │ + 77.92 %               ║
║ PS512 - fast-jwt (async with cache) │   10000 │ 155603.59 op/sec │  ± 4.27 % │ + 2049.16 %             ║
╟─────────────────────────────────────┼─────────┼──────────────────┼───────────┼─────────────────────────╢
║ Fastest test                        │ Samples │           Result │ Tolerance │ Difference with slowest ║
╟─────────────────────────────────────┼─────────┼──────────────────┼───────────┼─────────────────────────╢
║ PS512 - fast-jwt (sync with cache)  │   10000 │ 172097.91 op/sec │  ± 4.58 % │ + 2276.97 %             ║
╚═════════════════════════════════════╧═════════╧══════════════════╧═══════════╧═════════════════════════╝

╔═════════════════════════════════════╤═════════╤══════════════════╤═══════════╤═════════════════════════╗
║ Slower tests                        │ Samples │           Result │ Tolerance │ Difference with slowest ║
╟─────────────────────────────────────┼─────────┼──────────────────┼───────────┼─────────────────────────╢
║ EdDSA - fast-jwt (async)            │    1000 │   6370.58 op/sec │  ± 0.59 % │                         ║
║ EdDSA - jose (sync)                 │    1000 │   6538.90 op/sec │  ± 0.83 % │ + 2.64 %                ║
║ EdDSA - fast-jwt (sync)             │    1000 │   7078.93 op/sec │  ± 0.87 % │ + 11.12 %               ║
║ EdDSA - fast-jwt (async with cache) │   10000 │ 177457.09 op/sec │  ± 5.36 % │ + 2685.57 %             ║
╟─────────────────────────────────────┼─────────┼──────────────────┼───────────┼─────────────────────────╢
║ Fastest test                        │ Samples │           Result │ Tolerance │ Difference with slowest ║
╟─────────────────────────────────────┼─────────┼──────────────────┼───────────┼─────────────────────────╢
║ EdDSA - fast-jwt (sync with cache)  │   10000 │ 202628.41 op/sec │  ± 3.12 % │ + 3080.69 %             ║
╚═════════════════════════════════════╧═════════╧══════════════════╧═══════════╧═════════════════════════╝

Contributing

See CONTRIBUTING.md

License

Licensed under the Apache-2.0 license.

fast-jwt's People

Contributors

bredikhin avatar darkgl0w avatar dependabot-preview[bot] avatar dependabot[bot] avatar guilhermelimak avatar hails avatar ilteoood avatar krazylek avatar marco-ippolito avatar mcollina avatar melkornemesis avatar numtostr avatar olyop avatar optic-release-automation[bot] avatar ovhemert avatar panva avatar radomird avatar ramonmulia avatar relvao avatar rp4rk avatar rubiin avatar ryhinchey avatar s25g5d4 avatar salmanm avatar sameer-coder avatar shogunpanda avatar simoneb avatar timlehner avatar toni-sharpe avatar williamlines avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fast-jwt's Issues

Release pending!

Pending commits since release v1.3.0

Unreleased commits have been found which are pending release, please publish the changes.

  • 7069ad5 chore(deps): bump nearform/optic-release-automation from 2.1.3 to 2.1.4 (#132)
  • e26ef06 chore(deps): bump nearform/optic-release-automation from 2.1.2 to 2.1.3 (#130)
  • caa0075 chore(deps): bump nearform/optic-release-automation from 2.1.0 to 2.1.2 (#128)
  • ab15ab1 fix: bump optic-release-automation (#126)
  • 59cc40b fix: bump optic-release-automation (#124)
  • f8f4c71 fix: ci (#123)
  • 1ee85a7 chore(deps): bump nearform/optic-release-automation from 1.0.1 to 2.0.0 (#122)
  • 4b79b37 fix: release ci (#121)
  • 47c729d chore(deps): bump actions/checkout from 2.3.5 to 2.4.0 (#120)
  • 1ab4ce3 feat: Add support for ES* password protected private keys (#119)

Issue generated by github-actions-notify-release.

Aws Lambda Caching

Hi, if anyone's still monitoring this... how would caching work if you are verifying access tokens in an aws lambda function? Don't think there's a way to cache the function ( and it's memoized data?) that is created by createVerifier.

Release pending!

Pending commits since release v1.4.1

Unreleased commits have been found which are pending release, please publish the changes.

  • 0d39718 fix: clockTolerance property in verifier (#193)
  • a20b7af chore(deps-dev): bump @sinonjs/fake-timers from 8.1.0 to 9.0.0 (#189)
  • eafbf37 ci: add benchmarks run (#187)
  • 29f1d0a fix: updating pull request event (#188)
  • 84f2772 fix benchmarks (#186)
  • 81de028 Introduce `requiredClaims` option to the verifier (#184)
  • 2128fef chore: create .github/workflows/check-linked-issues.yml
  • 1ab078f chore: Updated CHANGELOG.md.

Issue generated by github-actions-notify-release.

Add a cache for async keys

in createVerified, we accept a key as a function. However we do not cache the fetched key. Adding caching would likely speed things up quite a bit in case of remote jwks keys.

feat: Add support for password protected private keys

The library currently supports keys that are either string, Buffer or a function but the key could be an Object representing a password protected private key as well, see the crypto documentation.

The format of the key is the following:

{
  key: "password_protected_private_key",
  passphrase: "secret"
}

I will create a PR for this issue.

unexpected TypeError

createVerifier({ key: 'foobar', checkTyp: 'jwt' })('eyJ0eXAiOjEsImFsZyI6IkhTMjU2In0.eyJpYXQiOjE1OTk1NzM1NTN9.H8YZrUYEhiK3A8RZdFVyS6JP_ymnZQFAkLuFnl1TIbg')
TypeError: (header.typ || "").toLowerCase is not a function
    at verifyToken (/Users/panva/.panvajs/node_modules/fast-jwt/src/verifier.js:176:42)
    at verify (/Users/panva/.panvajs/node_modules/fast-jwt/src/verifier.js:276:7)

This is related to my comment here. As a consumer of jwts i don't control i would expect to get a consistent error type for invalid JWTs

Add support for EdDSA tokens

Add support to sign and verify EdDSA tokens.
This also implies ensuring the hashKey method in utils.js chooses the right algorithm.

Supported curve and related cache keys hash algorithms are:

EdDSA curve Cache key hash
Ed25519 sha512
Ed448 curve shake256(..., 114)

Release pending!

Pending commits since release v1.2.0

Unreleased commits have been found which are pending release, please publish the changes.

  • dddd103 Support passphrase protected keys (#117)
  • 6ab7fe7 docs: remove readme deps badge
  • dfd4f6b chore(deps): bump actions/checkout from 2.3.4 to 2.3.5 (#114)
  • 14ee315 chore(deps-dev): bump tsd from 0.17.0 to 0.18.0 (#112)
  • ceda5eb chore(deps-dev): bump @sinonjs/fake-timers from 7.1.2 to 8.0.1 (#110)
  • cc242d0 chore(deps): bump actions/setup-node from 2.4.0 to 2.4.1 (#109)
  • 8d1a3b9 chore(deps): bump fastify/github-action-merge-dependabot (#108)
  • 68741e7 chore(deps-dev): bump eslint-config-standard-with-typescript (#107)
  • 997cb5b chore(deps-dev): bump cronometro from 0.8.0 to 1.0.0 (#106)
  • c1af32b chore(deps): bump fastify/github-action-merge-dependabot (#105)
  • 70ce802 chore(deps): bump fastify/github-action-merge-dependabot (#104)
  • 5b9e577 chore(deps): bump actions/setup-node from 2.3.2 to 2.4.0 (#103)
  • 9628755 chore(deps): bump actions/setup-node from 2.3.1 to 2.3.2 (#102)
  • 41bcc4d chore(deps): bump actions/setup-node from 2.3.0 to 2.3.1 (#101)
  • 40da1df chore(deps): bump actions/setup-node from 2.2.0 to 2.3.0 (#100)
  • 447d858 chore(deps-dev): bump @types/node from 15.14.1 to 16.0.0 (#98)
  • aa5dedd chore(deps): bump fastify/github-action-merge-dependabot (#97)
  • 73dbcbd chore(deps): bump actions/setup-node from 2.1.5 to 2.2.0 (#96)

Issue generated by github-actions-notify-release.

Missing types - `createSigner` options private key

I've created this #216 before, without creating an issue first since it's just a minor fix

Anyway, on that fix, I create a new interface:

interface PrivateKey {
  key: string | Buffer
  passphrase: string | undefined
}

instead of using nodejs crypto's PrivateKeyInput

20220406_02h08m44s_grim

I'll make a change if better to use the nodejs provided interface.

wrap caching around a library like jose

By looking at benchmarks I think most of performance is coming from caching verified token in LRU Cache ( I didn't do profiling myself on any jwt library ). isn't it better to just wrap caching around jose or node-jsonwebtoken ( i chose jose cause of eddsa. although jsonwebtoken is widely used and has many starrs, I didn't like the code ). I would appreciate some insight about this.
Thanks

Do not allow payloads that are not objects

The JWT spec mandates that the payloads MUST be objects.
From: https://tools.ietf.org/html/rfc7519#section-7.2

  1. Verify that the resulting octet sequence is a UTF-8-encoded
    representation of a completely valid JSON object conforming to
    RFC 7159 [RFC7159]; let the JWT Claims Set be this JSON object.

We should remove the json option from decoder and throw if the typ is not JWT in

if (json === true || header.typ === 'JWT') {
.

In the signer,

fast-jwt/src/signer.js

Lines 113 to 117 in 4f7fbfb

} else {
encodedPayload = Buffer.from(payload, 'utf-8')
.toString('base64')
.replace(base64UrlMatcher, base64UrlReplacer)
}
should be removed.

Token verification fails when token algorithm is not in the first position in allowedAlgorithms

Consider the following scenario:

const signedTokenAlgoHS = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxfQ.57TF7smP9XDhIexBqPC-F1toZReYZLWb_YRU5tv0sxM'

verify(signedTokenAlgoHS, {
      noTimestamp: true,
      algorithms: ['RS256', 'HS256']
})

// Will result in:
// Error: Invalid public key provided for algorithms RS256, HS256.

If the algorithms property is defined on the options, the verifer.js will check if the supplied algorithm list is compatible with the algorithm of the provided token. However the checkAreCompatibleAlgorithms function only checks for the first algorithm in the list:

fast-jwt/src/verifier.js

Lines 17 to 20 in 634783b

function checkAreCompatibleAlgorithms(expected, actual) {
const expectedType = expected[0].slice(0, 2)
const actualType = actual[0].slice(0, 2)

In our scenario above this will be marked as not valid (TokenError will be thrown) as it'll try to match the 'RS256' (expected) to 'HS256' (actual).

What should instead happen is that the method should loop through all of the expected algorithms and then if none of them match the actual then throw the TokenError.

Add generic types to create functions

I think it would be good to have these functions generically typed. If we know the shape of the payload, we can pass it here and get better type completion.

createSigner
createVerifier  

Would you accept a PR for this?

Add support for X509 public key certificates

In order to support fastify-auth0-verify, which sends public keys as x509 certificates (that are acquired remotely using a well-known URL containing a JWK set), for token verification it would be great if fast-jwt library would support it as well.

At this moment if we run the following scenario, the library throws an error:

const { createVerifier } = require('fast-jwt')

const publicKey = `
-----BEGIN CERTIFICATE-----
MIIFAzCCAuugAwIBAgIUVVehdczwTU8GW39JULd9pYi43ZkwDQYJKoZIhvcNAQEL
BQAwETEPMA0GA1UEAwwGdW51c2VkMB4XDTIxMTEzMDA5MDYyMVoXDTIxMTIzMDA5
MDYyMVowETEPMA0GA1UEAwwGdW51c2VkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEAwvS4K8Jnx2b1nuab16/cEslS5HqvKS/Qs7+9X3B9RdC5fT//lyWQ
1lj4Ag9rX+ewzfBjZksgtfYAp5gmXJl/+E+dYEIry2XkC2AJZ9voqTa+hld6a8YW
OmvITSNc8GinP73gXBcwcv20Ligyg3c7LTn5ZSaNwJixpgsi9/3qz0WK9ArylHgD
LQ4hEEKMicYvSWoC6VnnWANKZ0EphyXYplie0EdEbcoje0+7qncu64Mm2LwYaFW3
RR1ZwrmzCUjX2MV3/L2h/gI3kSIgrkxSZS0gdcvFO9uAu5tfcJbpJj9NR+ynH4CL
Rl8OT8F4lyCdO55QTJUMUDb1zLDurVLOWYvfxVejsvmz3/tQy+/T9uV2dFBzOx9I
AbcI+kjGSCF+APi/ShDnME8nnkxKMTC/NO2wC2sAqgwhH+4fl1Lb2lyIUrwmWVEG
JBajQ26j/iGUGGlpZW3dWHa7NKtVE5bS1U5HBBWuqPlyMqhr4Pa053uJ678ywGpk
yQan1E00YP67u36HAP8hNZtXWPTvHRasek8nEeKcemfFcu563eqaPjbCkieE0yUe
6m6OU5tD8TM+Gbce1OievtPEcJrctb+xoFZNZ0k4gTUIqyJ3F/n3o1x5wYFdf/wi
9feoTOr2kfhFQqarFPUZAgKqOKANRZdsl54TkApTmpWvtrpXOfYoUlUCAwEAAaNT
MFEwHQYDVR0OBBYEFDwRuiSKlx21kCz3yeZGxM+Qw9b0MB8GA1UdIwQYMBaAFDwR
uiSKlx21kCz3yeZGxM+Qw9b0MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
BQADggIBAAWS2FZI+ISovVC1gIbK+JAMlTGPpuawMe2N/38zLK1TUdA10rLqyQbF
31Sv/wj6IV6PDkuDhH3BzB04gQ0ZrIorqYG9wR3Y4ekcCr7pCkTbKo18I6TI7p3U
PN1t7W1VBt6PeXsXob+uhORhVtJH8+qqQswRlwobp9tF0xELJWHqs2JbWrfikb1R
tKv9IpsTXIyxab6iBGew4NLiGLEpk03ghjQLFWxC4/yvcF0TqZmSMO1IXDjSiK8t
6iBgLJFdyhSV7BTmHOV4ibdaEHdAfWmm4WvyQnHUZHIg4YgQuiyykqBHS1CLTIW4
sUjdDPJNTS7DKsKHrZUPnaOwQTRkkhjwC5tL6Fal+o8z1ogzbWJhhGeqq+KX6/Xb
K/NGMUhMdexh3fPmJ3wlEI2Ck7uni3CpPGnwckcoFpccwQjnkFj4TxhWDtf1yLxr
ne8EcVGQ9uuwsJjVboaujCovHChaRailpbBIV5Sc789iyLSZf+ylHv4dJcQ8UNJX
Wxqt7yJcfPnPGA3WGbvaJJuKtsWREE+Mf3ex7HL/RpX+6FX10m5GlwGKTNd0h2b8
qFPjP1tPZemAqxQntUkEGizSYxQ1bajAgrMjXeVNqz3Bah3WE7+pFbsH39h7fhtJ
L4HPfzHnmwS227fhAllgr8d7gc9vPgzLi9hKq6PMHqHRTPlytNMl
-----END CERTIFICATE-----
`

const token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IktFWSJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlzcyI6Imh0dHBzOi8vbG9jYWxob3N0LyJ9.vTYivYeaq09ZqDltEg7ELc6CXAWAg78LjmG-g7pqEm9xCIJ9amCS9tGfo_bwnAdr-VYb96vAVsZQfWVROQExGZCj6OxDxrZNcwN0Dv62axRhT2TrDKG9qzZMt_Lt92oLTVG0o3FAM8v_ZztjA5u2AMYWAA4xHuuj5Jf1ZbSIL7L0J5MJ62yg1xY2pV_5jUoVORBLo2XW7WtUkYZRrq4_tsAE5LgwSF83SPkScAF2p-MYOtz3RsjlAfGSAj5WyF4MnGCuQeC4jxH_UrpIf43cQpVliA-vRKr3hH_mPrnU-S8hI-acM69z_yfO3P28H_cn7Lc3sg6MGKJhuM4us1BWfYafDxdqbSaIvjKNCXaPxWSLgwOhEmjovNfluPRWnNR6CT3qEg3g7Mkobj1QKIbw8bO0UzpKBZHQEqLP_MJnHlGEG8m0tHIpD3GKJnVmlepX-0w1DtE02hdYOlr40E-LfOlTAFpMHkPvCsO6LdDkGILAvtng0qUXmHsKkCw18BgdS9_z9e9NqSOmuCxqeEdq41rFgjdKXjb8qCiTdDsip65zq__onsL_ugG-oHOBurzvmkClVY6H4JiKv-BIPueZHwe-SYxdb2aBzgaS85calY_zf2Otmy1E2FE-0D4V3OwJ2JaJGcvSnDcWdHC2BVCQQ4U4bEuASX_EJv52mn-R6r8'

const verifier = createVerifier({ key: publicKey, algorithms: ['RS256'] })
const payload = verifier(token)

console.log(payload)

// throws: TokenError: Invalid public key provided for algorithms RS256.

verify with `allowedIss` will be ignored if payload without iss data

Options with allowedIss to verify token. I think it should throw error if payload without iss value. Shouldn't it?

// file: test/example.spec.js

const { test } = require('tap')
const { createSigner, createVerifier } = require('../src')
const signSync = createSigner({ key: 'secret' })
const verifySync = createVerifier({ key: 'secret', allowedIss: 'foo.bar' })

// passing
test('token payload with iss value', async (t) => {
  const tokenPayloadWithIss = signSync({ a: 1, b: 2, c: 3, iss: 'bad' })
  t.throws(
    () => {
      verifySync(tokenPayloadWithIss, (err) => {
        console.log(err)
      })
    },
    {
      code: 'FAST_JWT_INVALID_CLAIM_VALUE',
      message: 'The iss claim value is not allowed.'
    }
  )
  t.end()
})

// failing
test('token payload without iss value', async (t) => {
  const tokenPayloadWithoutIss = signSync({ a: 1, b: 2, c: 3 })
  t.throws(() => {
    verifySync(
      tokenPayloadWithoutIss,
      (err) => {
        console.log(err)
      },
      {
        code: 'FAST_JWT_INVALID_CLAIM_VALUE',
        message: 'The iss claim value is not allowed.'
      }
    )
  })
  t.end()
})

and it also happen verify with allowedJti, allowedAud, allowedSub, and allowedNonce

Possibility of async crypto performance improvements

Hey there,

I just read your blog article and noticed the mention of Node's crypto slowing things down, and the IPC attempts to improve the performance.

I use this module in some other applications and I've had good performance with it - but I'm dealing with significantly larger chunks of data. Not sure if the increased context switching for such small payloads would yield a net positive or negative effect (I suspect negative based on your IPC experience), but I figured I'd drop this here just in case you wanted to give it a try:

https://github.com/ronomon/crypto-async

It's not possible to run the benchmarks, issues with `cronometro` and ES Modules

Currently, it's not possible to run the benchmark scripts.
When you try to run them (for example with npm run benchmark:decode), Node.js will refuse to execute the files with the following error:

Error [ERR_REQUIRE_ESM]: require() of ES Module /Users/hails/src/github.com/nearform/fast-jwt/node_modules/cronometro/dist/index.js from /Users/hails/src/github.com/nearform/fast-jwt/benchmarks/utils.js not supported.
Instead change the require of index.js in /Users/hails/src/github.com/nearform/fast-jwt/benchmarks/utils.js to a dynamic import() which is available in all CommonJS modules.
    at Object.<anonymous> (/Users/hails/src/github.com/nearform/fast-jwt/benchmarks/utils.js:3:20)
    at Object.<anonymous> (/Users/hails/src/github.com/nearform/fast-jwt/benchmarks/decode.js:3:39) {
  code: 'ERR_REQUIRE_ESM'
}

The problem occurs because cronometro is defined as an ES Module and and is not compatible with CommonJS. Instead of just using require, we should use the dynamic import approach, as described in this gist by @ShogunPanda

sub always overwritten

When trying to sign a payload the sub property gets alway overwritten, even when it's not passed to createSigner.

Reproduction steps:

const { createSigner, createDecoder } = require('fast-jwt')

const sign = createSigner({
  key: 'secret'
})

const decode = createDecoder()

const token = sign({
  id: 'id1',
  sub: 'test'
})

const decoded = decode(token)

console.log(decoded.sub)

Release pending!

Pending commits since release v1.1.3

Unreleased commits have been found which are pending release, please publish the changes.

  • 28c9f35 chore(deps): bump nearform/github-action-notify-release (#95)
  • a82b231 chore(deps-dev): bump tsd from 0.16.0 to 0.17.0 (#94)
  • b416c87 chore(deps-dev): bump tsd from 0.15.1 to 0.16.0 (#92)
  • 933f90e chore(deps): bump nearform/github-action-notify-release (#91)
  • f8080c8 chore(deps): bump fastify/github-action-merge-dependabot (#90)
  • 06f2df8 chore(deps): bump nearform/github-action-notify-release (#87)
  • 52ece30 chore(deps): bump actions/cache from 2.1.5 to 2.1.6 (#88)
  • f4b7cf2 chore(deps): bump fastify/github-action-merge-dependabot (#86)
  • f08b26a ci: add github-actions-release-notify (#85)
  • 373779c chore(deps-dev): bump eslint-plugin-promise from 4.3.1 to 5.1.0 (#84)
  • 2969d10 chore(deps-dev): bump tsd from 0.14.0 to 0.15.0 (#83)
  • 2cd38e0 chore(deps-dev): bump @types/node from 14.14.42 to 15.0.0 (#81)
  • c403cd1 chore(deps): bump actions/cache from v2.1.4 to v2.1.5 (#78)
  • 4529c42 chore(deps-dev): bump tap from 14.11.0 to 15.0.2 (#75)
  • 28ebf6e chore(deps): bump actions/cache from v2 to v2.1.4 (#72)
  • 80cf62d chore(deps): bump actions/checkout from v1 to v2.3.4 (#71)
  • 25230ee chore(deps): bump actions/setup-node from v1 to v2.1.5 (#74)
  • 1fce544 chore(deps): bump fastify/github-action-merge-dependabot (#73)
  • c839d72 chore: fix dependabot.yml file name
  • 5f0ba28 docs: reference get-jwks for JWKS support

Issue generated by github-actions-notify-release.

Support custom exp claim

Hi!

I'm working on a feature where I need to set a different expiration for each token I sign.
I tried to do set a "exp" field but it didn't work:

const { createSigner } = require('fast-jwt')
const signSync = createSigner({ key: 'secret' })
const token = signSync({
  a: 1,
  exp: Math.floor(Date.now() / 1000) + (60 * 60),
})

Looking at the code, I saw that "exp" is overwritten by "undefined" if "expiresIn" is not set.

const finalPayload = {
    ...payload,
    ...fixedPayload,
    iat: noTimestamp ? undefined : Math.floor(iat / 1000),
    exp: expiresIn ? Math.floor((iat + expiresIn) / 1000) : undefined,
    nbf: notBefore ? Math.floor((iat + notBefore) / 1000) : undefined
  }

Would it be possible to change the "finalPayload" to exp: expiresIn? Math.floor ((iat + expiresIn) / 1000): payload.exp or have some other way of passing a custom "exp"?

Thanks!

Release pending!

Pending commits since release v1.4.0

Unreleased commits have been found which are pending release, please publish the changes.

  • c239279 chore(deps): bump actions/setup-node from 2.5.0 to 2.5.1 (#176)
  • fe72c85 chore: use major version of notify release action
  • a109efc chore(deps): bump nearform/github-action-notify-release (#175)
  • f40fde5 chore(deps-dev): bump @types/node from 16.11.14 to 17.0.1 (#172)
  • 0190518 Update release.yml to use the new token naming convention (#169)
  • 55f5cba chore(deps): bump fastify/github-action-merge-dependabot from 2.7.1 to 3.0.2 (#168)
  • d034064 chore(deps): bump fastify/github-action-merge-dependabot (#166)

Issue generated by github-actions-notify-release.

Release pending!

Pending commits since release v1.3.2

Unreleased commits have been found which are pending release, please publish the changes.

  • 47ae343 v1.4.0 (#163)
  • 2983422 Added support for x509 certificate public key (#161)
  • 0dd9261 Refactoring checkAreCompatibleAlgorithms function (#158)
  • 634783b chore(deps): bump actions/setup-node from 2.4.1 to 2.5.0 (#156)
  • eff9cfe chore(deps): bump actions/cache from 2.1.6 to 2.1.7 (#154)
  • 9dd75bc Added JwtHeader type definition (#153)
  • cbd78c4 refactor (docs): Improve documentation (#151)
  • 321acdf chore(deps): bump mnemonist from 0.38.5 to 0.39.0 (#150)
  • 39bc143 chore(deps-dev): bump tsd from 0.18.0 to 0.19.0 (#149)
  • e6024b6 chore(deps): bump fastify/github-action-merge-dependabot (#148)
  • 8bd3de3 refactor (docs): fix `mutatePayload` definition sentence (#146)
  • 5fd8700 chore: use main for optic action to test (#144)

Issue generated by github-actions-notify-release.

Experimental

The README states "This module is experimental". What is needed to push this library to a non-experimental phase? I recently integrated fast-jwt with hapi for use in our microservices and all unit and integration tests passed. I love the simplicity of this lib and would be willing to help move this effort along.

Automated release not working

The release workflow is failing because it's missing a build step (npm ci). The build step can be added as a parameter to the optic-release-automation-action under build-command.

As part of this ticket we should remove the unnecessary ci script from the package.json (and replace the usage of it with appropriate scripts lint + test).

Add JwtHeader definition to the type definitions

The following type definition should be added to the src/index.d.ts file:

export interface JwtHeader {
    alg: string | Algorithm;
    typ?: string | undefined;
    cty?: string | undefined;
    crit?: Array<string | Exclude<keyof JwtHeader, 'crit'>> | undefined;
    kid?: string | undefined;
    jku?: string | undefined;
    x5u?: string | string[] | undefined;
    'x5t#S256'?: string | undefined;
    x5t?: string | undefined;
    x5c?: string | string[] | undefined;
}

This type is used in (fastify-jwt)[https://github.com/fastify/fastify-jwt] specifically is part of this PR: fastify/fastify-jwt#184

It was suggested to move it from fastify-jwt to here as it makes more sense and then import it in fastify-jwt along with other types already imported from this library.

Expired token is being accepted

We recently integrated fast-jwt with an API implemented in restify due to some performance issues in token verification. However, the tokens even though expired are still being allowed. See below the implementation:

const verifyWithCallback = createVerifier({ 
                    key: getKey, 
                    complete: true,
                    cache: true,
                    ignoreExpiration: false,
                    algorithms: ['HS256','HS384', 'HS512','RS256']
                })
                
                let encodedToken = token
                console.log('type: '+typeof(encodedToken))

                await verifyWithCallback(encodedToken, (err, sections) => {
                    if(err){
                        console.log(err)
                        res.send(400)
                        return
                    }
                    else{
                        const decode = createDecoder()
                        let payload = decode(encodedToken)
                       ...

Looking at the verify,js file, I saw a line there that terminates the execution if the type of the payload is string. I'm not sure if that has a bearing.

Drop the cache for sign

I'm not convinced this would be useful for end users, and it can possibly create an attack vector.

Caching JWS tokens to improve performance

The problem is that verifying tokens on every request is expensive from the resources point of view.
Caching the tokens without any mechanism of verification is also risky as there can be tokens used as valid when they shouldn't (e.g. key used to sign the token has expired yet the token is stored in the cache.

In order to prevent that I was playing with the idea of a reverse leaking bucket algorithm or a time based cache.

The first option would be something similar to:

Token gets verified (verify signature) and stored in a cache together with a "credit value" e.g. 10.
Every time the token is used, the credit value gets decreased by 1. When the value reaches 0, the
token gets verified again and stored in the cache.

That way, users of the service with more active connections will lead into more verifications and though it is not a ful fledged solution, it would balance speed and security: a credit of 1, will enforce a verification every time the token is used.

The second option is a timed cache:

Token gets verified and stored in a cache. Alongside the token, we store a time for when the token is valid that does not change depending on usage. This way we only verify the token on 1 call every x time units. Once the X time units lapses, a worker process (cannot be done on a timer or your sever can potentially choke the CPU every X time units if you have a large volume of tokens) will reverify the signature of all the tokens and evict the ones that canot be verified (e.g. public key has expired and so on).

Both options, for the shake of the speed should store:

  • The raw token
  • The parsed token

The reason for that is make sure that when a auth consumer wants to check any of the JWT claims, we don't need to parse the token (it is already parsed).

Token having a`nbf` claim and cached won't become active

This is the current behavior having problems:

const notBefore = 1000
const token = createSigner({ key: 'secret', notBefore })({ a: 1 })
const verify = createVerifier({ key: 'secret', cache: true })
try {
  verify(token)
} catch (err) { // throws with a 'FAST_JWT_INACTIVE' code, alright
  console.error(err)
}

setTimeout(() => {
  try {
    verify(token)
  } catch (err) { // also throws with 'FAST_JWT_INACTIVE', but should not
    console.error(err)
  }
}, 2000)

The fact is current tests are passing because of an uneven behavior for comparing time between cache item and token properties. They use a timestamp where now === min so they are able to pass, but will unlikely happen in real life.

// Check the cache with `now > min`
if (typeof value !== 'undefined' && (min === 0 || now > min) && (max === 0 || now <= max)) {

But:

// check the claim with `now >= adjusted `
const valid = greater ? now >= adjusted : now <= adjusted

Cache keys

Hi!

Was looking through the crypto of this module and one thing I was wondering about was the rationale for the algorithm used to protect the cache:

fast-jwt/src/utils.js

Lines 45 to 59 in b0893cf

function hashToken(token) {
const rawHeader = token.split('.', 1)[0]
const header = Buffer.from(rawHeader, 'base64').toString('utf-8')
let hasher = null
/* istanbul ignore next */
if (header.match(edAlgorithmMatcher) && header.match(ed448CurveMatcher)) {
hasher = createHash('shake256', { outputLength: 114 })
} else {
const mo = header.match(algorithmMatcher)
hasher = createHash(`sha${mo ? mo[1] : '512'}`)
}
return hasher.update(token).digest('hex')
}

Looking through the usage in the code, it seems that the key into the cache is always the hash of the full JWT token. The algorithm for hashing the tokens changes depending on the algorithm used in the header of the token. The accompanying blog post mentions:

To guarantee that the cache data is not compromised in case of unauthorized memory access, tokens are indexed in cache using a hashing algorithm which is chosen depending on the token encryption. For instance, HS384 encrypted token are hashed using the SHA384 algorithm, while EdDSA encrypted tokens with Ed448 curve are hashed using the SHAKE256 algorithm.

However, it doesn't mention why it's important to change the hash function.

The same function could be used for all key types, and simplify the code with less branches, using a HMAC with a random key generated on startup (fine since this is an in-memory cache). This would also protect against potential timing attacks in the lookup, since the key into the cache now uses a secret (I don't immediately see how one could find a distinguisher to perform such an attack with the current implementation, but maybe there's something I couldn't think of);

const { createHmac } = require('crypto')

const blindingKey = crypto.randomBytes(64) // Key / block size for BLAKE2s-256

module.exports = function key (data) {
  return createHmac('blake2s256', blindingKey).update(data).digest('hex')
}

Something like SipHash would also be an ideal candidate for this, and yield much smaller keys, saving memory. However I don't think that's available in core

Obviously there's a reason why you wrote it the way you did, and I could have completely misunderstood!

`clockTolerance` option for verifier is not considered

Hi there,

When I set clockTolerance in createVerifier, it does not have any effect.

As a second point, in the documentation it is said that this value is added to the current timestamp.
It is enough to validate the expiry, but in my case I would also like it to have effect on the notBefore property of the token.

The use case is if a token provider generates a token with nbf set at the current time, but the validation server clock is slightly lagging behind (even a couple of seconds), and will cause a token validation error.

I can send a PR fixing the tolerance for both expiry and notBefore if you are interested.

// classic expiry case, fails
const expiresIn = 1000
const token1 = createSigner({ key: 'secret', expiresIn })({ a: 1 })

setTimeout(() => {
  try {
    createVerifier({ key: 'secret', clockTolerance: 50000 })(token1)
  } catch (err) {
    console.error(err)
  }
}, 1000)

// notBefore case, fails as well
const clockTimestamp = Date.now() + 1000
const notBefore = 1
const token2 = createSigner({ key: 'secret', clockTimestamp, notBefore })({ a: 1 })

try {
  createVerifier({ key: 'secret', clockTolerance: 50000 })(token2)
} catch (err) {
  console.error(err)
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.