Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Replacing jsonwebtoken with fast-jwt #184

Merged
merged 29 commits into from
Nov 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
897cf93
feat: Replaced jsonwebtoken with fast-jwt
radomird Oct 19, 2021
b06b07a
Removed jsowebtoken library from the source and package.json;
radomird Oct 19, 2021
09b8e68
Updated tests to use new option names;
radomird Oct 22, 2021
9d284bf
Enabled all passing tests
radomird Oct 25, 2021
ac38fc6
Fixed all unit tests
radomird Oct 25, 2021
ba0e155
Added missing code coverage
radomird Oct 25, 2021
dfc1562
Updated types
radomird Oct 25, 2021
34148a4
Updated docs
radomird Oct 25, 2021
6a6d3b4
Removing unnecessary comment in jwt.d.ts
radomird Oct 25, 2021
df87c80
Added TODO task for disabled tests
radomird Oct 25, 2021
bc36ce1
Extracting repeated code to a new function
radomird Oct 26, 2021
2aa247b
Refactored repeating code
radomird Oct 26, 2021
cc97627
Removed console.log statements
radomird Oct 27, 2021
b482963
Updated fast-jwt to v1.3.2 and enabled tests
radomird Nov 10, 2021
ebc8b3f
Updated the min required fastify to a stable version
radomird Nov 10, 2021
e7c754f
docs: Updated README with a note about migration
radomird Nov 10, 2021
36df788
Update README.md
radomird Nov 10, 2021
00f3065
Added conversion to ms
radomird Nov 16, 2021
f0f4706
chore: Removed non compatible Node v10 from the CI build
radomird Nov 16, 2021
e6da278
Extracted signer/verifier/decoder init for reusability
radomird Nov 16, 2021
18d9d74
Apply suggestions from code review
radomird Nov 17, 2021
3665579
Update README.md
radomird Nov 17, 2021
e18137f
Apply suggestions from code review
radomird Nov 18, 2021
3832bff
Added type definitions for node - fix failing CI build
radomird Nov 18, 2021
c38ef95
chore: Updated docs with migration breaking changes and updated examples
radomird Nov 19, 2021
c9ad90a
fix: typo in README
radomird Nov 19, 2021
5c9ae34
Added missing option in migration section in README
radomird Nov 19, 2021
bd7e2b5
chore: Moved migration notes to separate file
radomird Nov 19, 2021
bb6dd06
Added few more migration points to UPGRADING.md
radomird Nov 22, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:

strategy:
matrix:
node-version: [10, 12, 14, 16]
node-version: [12, 14, 16]
climba03003 marked this conversation as resolved.
Show resolved Hide resolved
os: [macos-latest, ubuntu-latest, windows-latest]

steps:
Expand Down
118 changes: 62 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
[![Known Vulnerabilities](https://snyk.io/test/github/fastify/fastify-jwt/badge.svg)](https://snyk.io/test/github/fastify/fastify-jwt)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/)

JWT utils for Fastify, internally uses [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken).
JWT utils for Fastify, internally it uses [fast-jwt](https://github.com/nearform/fast-jwt).

**NOTE:** The plugin has been migrated from using `jsonwebtoken` to `fast-jwt`. Even though `fast-jwt` has 1:1 feature implementation with `jsonwebtoken`, some _exotic_ implementations might break. In that case please open an issue with details of your implementation. See [Upgrading notes](UPGRADING.md) for more details about what changes this migration introduced.

`fastify-jwt` supports Fastify@3.
`fastify-jwt` [v1.x](https://github.com/fastify/fastify-jwt/tree/1.x)
Expand All @@ -17,7 +19,7 @@ npm i fastify-jwt --save
```

## Usage
Register as a plugin. This will decorate your `fastify` instance with the standard [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) methods `decode`, `sign`, and `verify`; refer to their documentation to find how to use the utilities. It will also register `request.jwtVerify` and `reply.jwtSign`. You must pass a `secret` when registering the plugin.
Register as a plugin. This will decorate your `fastify` instance with the following methods: `decode`, `sign`, and `verify`; refer to their documentation to find how to use the utilities. It will also register `request.jwtVerify` and `reply.jwtSign`. You must pass a `secret` when registering the plugin.

```js
const fastify = require('fastify')()
Expand Down Expand Up @@ -185,16 +187,16 @@ fastify.register(jwt, {
// Global default signing method options
sign: {
algorithm: 'ES256',
issuer: 'api.example.tld'
iss: 'api.example.tld'
},
// Global default verifying method options
verify: { issuer: 'api.example.tld' }
verify: { allowedIss: 'api.example.tld' }
})

fastify.get('/decode', async (request, reply) => {
// We clone the global signing options before modifying them
let altSignOptions = Object.assign({}, fastify.jwt.options.sign)
altSignOptions.issuer = 'another.example.tld'
altSignOptions.iss = 'another.example.tld'

// We generate a token using the default sign options
const token = await reply.jwtSign({ foo: 'bar' })
Expand Down Expand Up @@ -411,43 +413,39 @@ fastify.register(require('fastify-jwt'), {

### `decode`

* `json`: force JSON.parse on the payload even if the header doesn't contain `"typ":"JWT"`.
* `complete`: return an object with the decoded payload and header.
* `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`.

### `sign`

* `algorithm` (default: `HS256`)
* `expiresIn`: expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms). E.g.: `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"`).
* `notBefore`: expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms). E.g.: `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"`).
* `audience`
* `issuer`
* `jwtid`
* `subject`
* `noTimestamp`
* `header`
* `keyid`
* `mutatePayload`: if true, the sign function will modify the payload object directly. 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.
* `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. If provided, it will override the value of [secret](#secret-required) provided in the options.
* `algorithm`: The algorithm to use to sign the token. The default is autodetected from the key, using RS256 for RSA private keys, HS256 for plain secrets and the correspondent 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.
* `expiresIn`: Time span after which the token expires, added as the `exp` claim in the payload. It is expressed in seconds or a string describing a time span (E.g.: `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"`). This will override any existing value in the claim.
* `notBefore`: Time span before the token is active, added as the `nbf` claim in the payload. It is expressed in seconds or a string describing a time span (E.g.: `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"`). This will override any existing value in the claim.

* ... the rest of the **sign** options can be found [here](https://github.com/nearform/fast-jwt#createsigner).

### `verify`

* `algorithms`: List of strings with the names of the allowed algorithms. For instance, `["HS256", "HS384"]`.
* `audience`: if you want to check audience (`aud`), provide a value here. The audience can be checked against a string, a regular expression or a list of strings and/or regular expressions. E.g.: `"urn:foo"`, `/urn:f[o]{2}/`, `[/urn:f[o]{2}/, "urn:bar"]`
* `issuer` (optional): string or array of strings of valid values for the `iss` field.
* `ignoreExpiration`: if `true` do not validate the expiration of the token.
* `ignoreNotBefore`...
* `subject`: if you want to check subject (`sub`), provide a value here
* `clockTolerance`: number of seconds to tolerate when checking the `nbf` and `exp` claims, to deal with small clock differences among different servers
* `maxAge`: the maximum allowed age for tokens to still be valid. It is expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms). E.g.: `1000`, `"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"`).
* `clockTimestamp`: the time in seconds that should be used as the current time for all necessary comparisons.
* `extractToken(request): token`: Callback function allowing to use custom logic to extract the JWT token from the request.
* `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. If provided, it will override the value of [secret](#secret-required) provided in the options.
* `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 this to `true` is equivalent to provide the size 1000. When enabled the performance is dramatically improved. By default the cache is disabled.
mcollina marked this conversation as resolved.
Show resolved Hide resolved
* `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.
radomird marked this conversation as resolved.
Show resolved Hide resolved
* `maxAge`: The maximum allowed age for tokens to still be valid. It is expressed in seconds or a string describing a time span (E.g.: `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"`). By default this is not checked.
* ... the rest of the **verify** options can be found [here](https://github.com/nearform/fast-jwt#createverifier).

## API Spec

### fastify.jwt.sign(payload [,options] [,callback])
The `sign` method is an implementation of [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken#jwtsignpayload-secretorprivatekey-options-callback) `.sign()`. Can be used asynchronously by passing a callback function; synchronously without a callback.
This method is used to sign the provided `payload`. It returns the token.
The `payload` must be an `Object`. Can be used asynchronously by passing a callback function; synchronously without a callback.
`options` must be an `Object` and can contain [sign](#sign) options.

### fastify.jwt.verify(token, [,options] [,callback])
The `verify` method is an implementation of [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken#jwtverifytoken-secretorpublickey-options-callback) `.verify()`. Can be used asynchronously by passing a callback function; synchronously without a callback.
This method is used to verify provided token. It accepts a `token` (as `Buffer` or a `string`) and returns the payload or the sections of the token. Can be used asynchronously by passing a callback function; synchronously without a callback.
`options` must be an `Object` and can contain [verify](#verify) options.

#### Example
```js
Expand All @@ -462,7 +460,9 @@ fastify.jwt.verify(token, (err, decoded) => {
```

### fastify.jwt.decode(token [,options])
The `decode` method is an implementation of [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken#jwtdecodetoken--options) `.decode()`. Can only be used synchronously.
This method is used to decode the provided token. It accepts a token (as a `Buffer` or a `string`) and returns the payload or the sections of the token.
`options` must be an `Object` and can contain [decode](#decode) options.
Can only be used synchronously.

#### Example
```js
Expand All @@ -487,12 +487,12 @@ fastify.register(jwt, {
},
sign: {
algorithm: 'RS256',
audience: 'foo',
issuer: 'example.tld'
aud: 'foo',
iss: 'example.tld'
},
verify: {
audience: 'foo',
issuer: 'example.tld',
allowedAud: 'foo',
allowedIss: 'example.tld',
}
})

Expand All @@ -502,8 +502,8 @@ fastify.get('/', (request, reply) => {
// We recommend that you clone the options like this when you need to mutate them
// modifiedVerifyOptions = { audience: 'foo', issuer: 'example.tld' }
let modifiedVerifyOptions = Object.assign({}, fastify.jwt.options.verify)
modifiedVerifyOptions.audience = 'bar'
modifiedVerifyOptions.subject = 'test'
modifiedVerifyOptions.allowedAud = 'bar'
modifiedVerifyOptions.allowedSub = 'test'

return { globalOptions, modifiedVerifyOptions }
/**
Expand All @@ -513,18 +513,18 @@ fastify.get('/', (request, reply) => {
* decode: {},
* sign: {
* algorithm: 'RS256',
* audience: 'foo',
* issuer: 'example.tld'
* aud: 'foo',
* iss: 'example.tld'
* },
* verify: {
* audience: 'foo',
* issuer: 'example.tld'
* allowedAud: 'foo',
* allowedIss: 'example.tld'
* }
* },
* modifiedVerifyOptions: {
* audience: 'bar',
* issuer: 'example.tld',
* subject: 'test'
* allowedAud: 'bar',
* allowedIss: 'example.tld',
* allowedSub: 'test'
* }
* }
*/
Expand Down Expand Up @@ -555,20 +555,26 @@ As of 3.2.0, decorated when `options.jwtDecode` is truthy. Will become non-condi

### Algorithms supported

The following algorithms are currently supported by [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken#algorithms-supported) that is internally used by `fastify-jwt`.
The following algorithms are currently supported by [fast-jwt](https://github.com/nearform/fast-jwt) that is internally used by `fastify-jwt`.

algorithm(s) Parameter Value | Digital Signature or MAC Algorithm
**Name** | **Description**
----------------|----------------------------
HS256 | HMAC using SHA-256 hash algorithm
HS384 | HMAC using SHA-384 hash algorithm
HS512 | HMAC using SHA-512 hash algorithm
RS256 | RSASSA using SHA-256 hash algorithm
RS384 | RSASSA using SHA-384 hash algorithm
RS512 | RSASSA 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
none | No digital signature or MAC value included
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+

You can find the list [here](https://github.com/nearform/fast-jwt#algorithms-supported).

### Examples

Expand Down
23 changes: 23 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
## Upgrading Notes
This document captures breaking changes between versions of `fastify-jwt`.

### Upgrading from 3.x to 4.0

In `v4` we migrated away from using `jsonwebtoken` to `fast-jwt`. This introduced the following breaking changes:
mcollina marked this conversation as resolved.
Show resolved Hide resolved
- **sign** options:
- `audience` should be changed to `aud`
- `issuer` should be changed to `iss`
- `jwtid` should be changed to `jti`
- `subject` should be changed to `sub`
- `keyId` should be changed to `kid`

- **verify** options:
- `audience` should be changed to `allowedAud`
- `issuer` should be changed to `allowedIss`
- `subject` should be changed to `allowedSub`
- `jwtid` should be changed to `allowedJti`
- `nonce` should be changed to `allowedNonce`

- **decode** options:
- `json` option has been removed
- `checkTyp` option has been introduced. If set to a string value, a check of the `typ` header claim is forced. Example: `checkTyp: 'JWT'`. By default `checkTyp` is `undefined`.
Loading