Giter Site home page Giter Site logo

fastify-jwt's Introduction

@fastify/jwt

CI NPM version js-standard-style

JWT utils for Fastify, internally it uses 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 for more details about what changes this migration introduced.

@fastify/jwt supports Fastify@3. @fastify/jwt v1.x supports both Fastify@2.

Install

npm i @fastify/jwt

Usage

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.

const fastify = require('fastify')()
fastify.register(require('@fastify/jwt'), {
  secret: 'supersecret'
})

fastify.post('/signup', (req, reply) => {
  // some code
  const token = fastify.jwt.sign({ payload })
  reply.send({ token })
})

fastify.listen({ port: 3000 }, err => {
  if (err) throw err
})

For verifying & accessing the decoded token inside your services, you can use a global onRequest hook to define the verification process like so:

const fastify = require('fastify')()
fastify.register(require('@fastify/jwt'), {
  secret: 'supersecret'
})

fastify.addHook("onRequest", async (request, reply) => {
  try {
    await request.jwtVerify()
  } catch (err) {
    reply.send(err)
  }
})

Afterwards, just use request.user in order to retrieve the user information:

module.exports = async function(fastify, opts) {
  fastify.get("/", async function(request, reply) {
    return request.user
  })
}

However, most of the time we want to protect only some of the routes in our application. To achieve this you can wrap your authentication logic into a plugin like

const fp = require("fastify-plugin")

module.exports = fp(async function(fastify, opts) {
  fastify.register(require("@fastify/jwt"), {
    secret: "supersecret"
  })

  fastify.decorate("authenticate", async function(request, reply) {
    try {
      await request.jwtVerify()
    } catch (err) {
      reply.send(err)
    }
  })
})

Then use the onRequest of a route to protect it & access the user information inside:

module.exports = async function(fastify, opts) {
  fastify.get(
    "/",
    {
      onRequest: [fastify.authenticate]
    },
    async function(request, reply) {
      return request.user
    }
  )
}

Make sure that you also check @fastify/auth plugin for composing more complex strategies.

Auth0 tokens verification

If you need to verify Auth0 issued HS256 or RS256 JWT tokens, you can use fastify-auth0-verify, which is based on top of this module.

Options

secret (required)

You must pass a secret to the options parameter. The secret can be a primitive type String, a function that returns a String or an object { private, public }.

In this object { private, public } the private key is a string, buffer or object containing either the secret for HMAC algorithms or the PEM encoded private key for RSA and ECDSA. In case of a private key with passphrase an object { private: { key, passphrase }, public } can be used (based on crypto documentation), in this case be sure you pass the algorithm inside the signing options prefixed by the sign key of the plugin registering options).

In this object { private, public } the public key is a string or buffer containing either the secret for HMAC algorithms, or the PEM encoded public key for RSA and ECDSA.

Function based secret is supported by the request.jwtVerify() and reply.jwtSign() methods and is called with request, token, and callback parameters.

Verify-only mode

In cases where your incoming JWT tokens are issued by a trusted external service, and you need only to verify their signature without issuing, there is an option to configure fastify-jwt in verify-only mode by passing the secret object containing only a public key: { public }.

When only a public key is provided, decode and verification functions will work as described below, but an exception will be thrown at an attempt to use any form of sign functionality.

Example

const { readFileSync } = require('node:fs')
const path = require('node:path')
const fastify = require('fastify')()
const jwt = require('@fastify/jwt')
// secret as a string
fastify.register(jwt, { secret: 'supersecret' })
// secret as a function with callback
fastify.register(jwt, {
  secret: function (request, token, callback) {
    // do something
    callback(null, 'supersecret')
  }
})
// secret as a function returning a promise
fastify.register(jwt, {
  secret: function (request, token) {
    return Promise.resolve('supersecret')
  }
})
// secret as an async function
fastify.register(jwt, {
  secret: async function (request, token) {
    return 'supersecret'
  }
})
// secret as an object of RSA keys (without passphrase)
// the files are loaded as strings
fastify.register(jwt, {
  secret: {
    private: readFileSync(`${path.join(__dirname, 'certs')}/private.key`, 'utf8'),
    public: readFileSync(`${path.join(__dirname, 'certs')}/public.key`, 'utf8')
  },
  sign: { algorithm: 'RS256' }
})
// secret as an object of P-256 ECDSA keys (with a passphrase)
// the files are loaded as buffers
fastify.register(jwt, {
  secret: {
    private: {
      key: readFileSync(`${path.join(__dirname, 'certs')}/private.pem`),
      passphrase: 'super secret passphrase'
    },
    public: readFileSync(`${path.join(__dirname, 'certs')}/public.pem`)
  },
  sign: { algorithm: 'ES256' }
})
// secret as an object with RSA public key
// fastify-jwt is configured in VERIFY-ONLY mode
fastify.register(jwt, {
  secret: {
    public: process.env.JWT_ISSUER_PUBKEY
  }
})

Default options

Optionally you can define global default options that will be used by @fastify/jwt API if you do not override them.

Example

const { readFileSync } = require('node:fs')
const path = require('node:path')
const fastify = require('fastify')()
const jwt = require('@fastify/jwt')
fastify.register(jwt, {
  secret: {
    private: readFileSync(`${path.join(__dirname, 'certs')}/private.pem`, 'utf8')
    public: readFileSync(`${path.join(__dirname, 'certs')}/public.pem`, 'utf8')
  },
  // Global default decoding method options
  decode: { complete: true },
  // Global default signing method options
  sign: {
    algorithm: 'ES256',
    iss: 'api.example.tld'
  },
  // Global default verifying method options
  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.iss = 'another.example.tld'

  // We generate a token using the default sign options
  const token = await reply.jwtSign({ foo: 'bar' })
  // We generate a token using overrided options
  const tokenAlt = await reply.jwtSign({ foo: 'bar' }, altSignOptions)

  // We decode the token using the default options
  const decodedToken = fastify.jwt.decode(token)

  // We decode the token using completely overided the default options
  const decodedTokenAlt = fastify.jwt.decode(tokenAlt, { complete: false })

  return { decodedToken, decodedTokenAlt }
  /**
   * Will return:
   *
   * {
   *   "decodedToken": {
   *     "header": {
   *       "alg": "ES256",
   *       "typ": "JWT"
   *     },
   *     "payload": {
   *       "foo": "bar",
   *       "iat": 1540305336
   *       "iss": "api.example.tld"
   *     },
   *     "signature": "gVf5bzROYB4nPgQC0nbJTWCiJ3Ya51cyuP-N50cidYo"
   *   },
   *   decodedTokenAlt: {
   *     "foo": "bar",
   *     "iat": 1540305337
   *     "iss": "another.example.tld"
   *   },
   * }
   */
})

fastify.listen({ port: 3000 }, err => {
  if (err) throw err
})

cookie

Example using cookie

In some situations you may want to store a token in a cookie. This allows you to drastically reduce the attack surface of XSS on your web app with the httpOnly and secure flags. Cookies can be susceptible to CSRF. You can mitigate this by either setting the sameSite flag to strict, or by using a CSRF library such as @fastify/csrf.

Note: This plugin will look for a decorated request with the cookies property. @fastify/cookie supports this feature, and therefore you should use it when using the cookie feature. The plugin will fallback to looking for the token in the authorization header if either of the following happens (even if the cookie option is enabled):

  • The request has both the authorization and cookie header
  • Cookie is empty, authorization header is present

If you are signing your cookie, you can set the signed boolean to true which will make sure the JWT is verified using the unsigned value.

const fastify = require('fastify')()
const jwt = require('@fastify/jwt')

fastify.register(jwt, {
  secret: 'foobar',
  cookie: {
    cookieName: 'token',
    signed: false
  }
})

fastify
  .register(require('@fastify/cookie'))

fastify.get('/cookies', async (request, reply) => {
  const token = await reply.jwtSign({
    name: 'foo',
    role: ['admin', 'spy']
  })

  reply
    .setCookie('token', token, {
      domain: 'your.domain',
      path: '/',
      secure: true, // send cookie over HTTPS only
      httpOnly: true,
      sameSite: true // alternative CSRF protection
    })
    .code(200)
    .send('Cookie sent')
})

fastify.addHook('onRequest', (request) => request.jwtVerify())

fastify.get('/verifycookie', (request, reply) => {
  reply.send({ code: 'OK', message: 'it works!' })
})

fastify.listen({ port: 3000 }, err => {
  if (err) throw err
})

onlyCookie

Setting this options to true will decode only the cookie in the request. This is useful for refreshToken implementations where the request typically has two tokens: token and refreshToken. The main authentication token usually has a shorter timeout and the refresh token normally stored in the cookie has a longer timeout. This allows you to check to make sure that the cookie token is still valid, as it could have a different expiring time than the main token. The payloads of the two different tokens could also be different.

const fastify = require('fastify')()
const jwt = require('@fastify/jwt')

fastify.register(jwt, {
  secret: 'foobar',
  cookie: {
    cookieName: 'refreshToken',
  },
  sign: {
    expiresIn: '10m'
  }
})

fastify
  .register(require('@fastify/cookie'))

fastify.get('/cookies', async (request, reply) => {

  const token = await reply.jwtSign({
    name: 'foo'
  })

  const refreshToken = await reply.jwtSign({
    name: 'bar'
  }, {expiresIn: '1d'})

  reply
    .setCookie('refreshToken', refreshToken, {
      domain: 'your.domain',
      path: '/',
      secure: true, // send cookie over HTTPS only
      httpOnly: true,
      sameSite: true // alternative CSRF protection
    })
    .code(200)
    .send({token})
})

fastify.addHook('onRequest', (request) => request.jwtVerify({onlyCookie: true}))

fastify.get('/verifycookie', (request, reply) => {
  reply.send({ code: 'OK', message: 'it works!' })
})

fastify.listen({ port: 3000 }, err => {
  if (err) throw err
})

trusted

Additionally, it is also possible to reject tokens selectively (i.e. blacklisting) by providing the option trusted with the following signature: (request, decodedToken) => boolean|Promise<boolean>|SignPayloadType|Promise<SignPayloadType> where request is a FastifyRequest and decodedToken is the parsed (and verified) token information. Its result should be false or Promise<false> if the token should be rejected or, otherwise, be true or Promise<true> if the token should be accepted and, considering that request.user will be used after that, the return should be decodedToken itself.

Example trusted tokens

const fastify = require('fastify')()

fastify.register(require('@fastify/jwt'), {
  secret: 'foobar',
  trusted: validateToken
})

fastify.addHook('onRequest', (request) => request.jwtVerify())

fastify.get('/', (request, reply) => {
  reply.send({ code: 'OK', message: 'it works!' })
})

fastify.listen({ port: 3000 }, (err) => {
  if (err) {
    throw err
  }
})

// ideally this function would do a query against some sort of storage to determine its outcome
async function validateToken(request, decodedToken) {
  const denylist = ['token1', 'token2']

  return !denylist.includes(decodedToken.jti)
}

formatUser

Example with formatted user

You may customize the request.user object setting a custom sync function as parameter:

const fastify = require('fastify')();
fastify.register(require('@fastify/jwt'), {
  formatUser: function (user) {
    return {
      departmentName: user.department_name,
      name: user.name
    }
  },
  secret: 'supersecret'
});

fastify.addHook('onRequest', (request, reply) =>  request.jwtVerify());

fastify.get("/", async (request, reply) => {
  return `Hello, ${request.user.name} from ${request.user.departmentName}.`;
});

namespace

To define multiple JWT validators on the same routes, you may use the namespace option. You can combine this with custom names for jwtVerify, jwtDecode, and jwtSign.

When you omit the jwtVerify, jwtDecode, or jwtSign options, the default function name will be <namespace>JwtVerify, <namespace>JwtDecode and <namespace>JwtSign correspondingly.

Example with namespace

const fastify = require('fastify')

fastify.register(jwt, {
  secret: 'test',
  namespace: 'security',
  // will decorate request with `securityVerify`, `securitySign`,
  // and default `securityJwtDecode` since no custom alias provided
  jwtVerify: 'securityVerify',
  jwtSign: 'securitySign'
})

fastify.register(jwt, {
  secret: 'fastify',
  // will decorate request with default `airDropJwtVerify`, `airDropJwtSign`,
  // and `airDropJwtDecode` since no custom aliases provided
  namespace: 'airDrop'
})

// use them like this:
fastify.post('/sign/:namespace', async function (request, reply) {
  switch (request.params.namespace) {
    case 'security':
      return reply.securitySign(request.body)
    default:
      return reply.airDropJwtSign(request.body)
  }
})

extractToken

Setting this option will allow you to extract a token using function passed in for extractToken option. The function definition should be (request: FastifyRequest) => token. Fastify JWT will check if this option is set, if this option is set it will use the function defined in the option. When this option is not set then it will follow the normal flow.

const fastify = require('fastify')
const jwt = require('@fastify/jwt')

fastify.register(jwt, { secret: 'test', verify: { extractToken: (request) => request.headers.customauthheader } })

fastify.post('/sign', function (request, reply) {
  return reply.jwtSign(request.body)
    .then(function (token) {
      return { token }
    })
})

fastify.get('/verify', function (request, reply) {
  // token avaiable via `request.headers.customauthheader` as defined in fastify.register above
  return request.jwtVerify()
    .then(function (decodedToken) {
      return reply.send(decodedToken)
    })
})

fastify.listen(3000, function (err) {
  if (err) throw err
})

Typescript

To simplify the use of namespaces in TypeScript you can use the FastifyJwtNamespace helper type:

declare module 'fastify' {
  interface FastifyInstance extends 
  FastifyJwtNamespace<{namespace: 'security'}> {
  }
}

Alternatively you can type each key explicitly:

declare module 'fastify' {
  interface FastifyInstance extends 
  FastifyJwtNamespace<{
    jwtDecode: 'securityJwtDecode',
    jwtSign: 'securityJwtSign',
    jwtVerify: 'securityJwtVerify',
  }> { }
}

messages

For your convenience, you can override the default HTTP response messages sent when an unauthorized or bad request error occurs. You can choose the specific messages to override and the rest will fallback to the default messages. The object must be in the format specified in the example below.

Example

const fastify = require('fastify')

const myCustomMessages = {
  badRequestErrorMessage: 'Format is Authorization: Bearer [token]',
  badCookieRequestErrorMessage: 'Cookie could not be parsed in request',
  noAuthorizationInHeaderMessage: 'No Authorization was found in request.headers',
  noAuthorizationInCookieMessage: 'No Authorization was found in request.cookies',
  authorizationTokenExpiredMessage: 'Authorization token expired',
  authorizationTokenUntrusted: 'Untrusted authorization token',
  authorizationTokenUnsigned: 'Unsigned authorization token'
  // for the below message you can pass a sync function that must return a string as shown or a string
  authorizationTokenInvalid: (err) => {
    return `Authorization token is invalid: ${err.message}`
  }
}

fastify.register(require('@fastify/jwt'), {
  secret: 'supersecret',
  messages: myCustomMessages
})
Error Code

ERR_ASSERTION - Missing required parameter or option

  • Error Status Code: 500
  • Error Message: Missing ${required}

FST_JWT_BAD_REQUEST - Bad format in request authorization header. Example of correct format Authorization: Bearer [token]

  • Error Status Code: 400
  • Error Message: Format is Authorization: Bearer [token]

FST_JWT_BAD_COOKIE_REQUEST - Cookie could not be parsed in request object

  • Error Status Code: 400
  • Error Message: Cookie could not be parsed in request

FST_JWT_NO_AUTHORIZATION_IN_HEADER - No Authorization header was found in request.headers

  • Error Status Code: 401
  • Error Message: `No Authorization was found in request.headers

FST_JWT_NO_AUTHORIZATION_IN_COOKIE - No Authorization header was found in request.cookies

  • Error Status Code: 401
  • Error Message: No Authorization was found in request.cookies

FST_JWT_AUTHORIZATION_TOKEN_EXPIRED - Authorization token has expired

  • Error Status Code: 401
  • Error Message: Authorization token expired

FST_JWT_AUTHORIZATION_TOKEN_INVALID - Authorization token provided is invalid.

  • Error Status Code: 401
  • Error Message: Authorization token is invalid: ${err.message}

FST_JWT_AUTHORIZATION_TOKEN_UNTRUSTED - Untrusted authorization token was provided

  • Error Status Code: 401
  • Error Message: Untrusted authorization token

FAST_JWT_MISSING_SIGNATURE - Unsigned or missing authorization token

  • Error Status Code: 401
  • Error Message: Unsigned authorization token

decoratorName

If this plugin is used together with fastify/passport, we might get an error as both plugins use the same name for a decorator. We can change the name of the decorator, or user will default

Example

const fastify = require('fastify')
fastify.register(require('@fastify/jwt'), {
  secret: 'supersecret',
  decoratorName: 'customName'
})

decode

  • 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

  • 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 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.

verify

  • 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 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.
  • 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.
  • 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.

API Spec

fastify.jwt.sign(payload [,options] [,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 options.

fastify.jwt.verify(token, [,options] [,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 options.

Example

const token = fastify.jwt.sign({ foo: 'bar' })
// synchronously
const decoded = fastify.jwt.verify(token)
// asycnhronously
fastify.jwt.verify(token, (err, decoded) => {
  if (err) fastify.log.error(err)
  fastify.log.info(`Token verified. Foo is ${decoded.foo}`)
})

fastify.jwt.decode(token [,options])

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 options. Can only be used synchronously.

Example

const token = fastify.jwt.sign({ foo: 'bar' })
const decoded = fastify.jwt.decode(token)
fastify.log.info(`Decoded JWT: ${decoded}`)

fastify.jwt.options

For your convenience, the decode, sign, verify and messages options you specify during .register are made available via fastify.jwt.options that will return an object { decode, sign, verify, messages } containing your options.

Example

const { readFileSync } = require('node:fs')
const path = require('node:path')
const fastify = require('fastify')()
const jwt = require('@fastify/jwt')
fastify.register(jwt, {
  secret: {
    private: readFileSync(`${path.join(__dirname, 'certs')}/private.key`),
    public: readFileSync(`${path.join(__dirname, 'certs')}/public.key`)
  },
  sign: {
    algorithm: 'RS256',
    aud: 'foo',
    iss: 'example.tld'
  },
  verify: {
    allowedAud: 'foo',
    allowedIss: 'example.tld',
  }
})

fastify.get('/', (request, reply) => {
  const globalOptions = fastify.jwt.options

  // 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.allowedAud = 'bar'
  modifiedVerifyOptions.allowedSub = 'test'

  return { globalOptions, modifiedVerifyOptions }
  /**
   * Will return :
   * {
   *   globalOptions: {
   *     decode: {},
   *     sign: {
   *       algorithm: 'RS256',
   *       aud: 'foo',
   *       iss: 'example.tld'
   *     },
   *     verify: {
   *       allowedAud: 'foo',
   *       allowedIss: 'example.tld'
   *     }
   *   },
   *   modifiedVerifyOptions: {
   *     allowedAud: 'bar',
   *     allowedIss: 'example.tld',
   *     allowedSub: 'test'
   *   }
   * }
   */
})

fastify.listen({ port: 3000 }, err => {
  if (err) throw err
})

fastify.jwt.cookie

For your convenience, request.jwtVerify() will look for the token in the cookies property of the decorated request. You must specify cookieName. Refer to the cookie example to see sample usage and important caveats.

reply.jwtSign(payload, [options,] callback)

options must be an Object and can contain sign options.

request.jwtVerify([options,] callback)

options must be an Object and can contain verify and decode options.

request.jwtDecode([options,] callback)

Decode a JWT without verifying.

options must be an Object and can contain verify and decode options.

Algorithms supported

The following algorithms are currently supported by fast-jwt that is internally used by @fastify/jwt.

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+

You can find the list here.

Examples

Certificates Generation

Here some example on how to generate certificates and use them, with or without passphrase.

Signing and verifying (jwtSign, jwtVerify)

const fastify = require('fastify')()
const jwt = require('@fastify/jwt')
const request = require('request')

fastify.register(jwt, {
  secret: function (request, reply, callback) {
    // do something
    callback(null, 'supersecret')
  }
})

fastify.post('/sign', function (request, reply) {
  reply.jwtSign(request.body.payload, function (err, token) {
    return reply.send(err || { 'token': token })
  })
})

fastify.get('/verify', function (request, reply) {
  request.jwtVerify(function (err, decoded) {
    return reply.send(err || decoded)
  })
})

fastify.listen({ port: 3000 }, function (err) {
  if (err) fastify.log.error(err)
  fastify.log.info(`Server live on port: ${fastify.server.address().port}`)

  // sign payload and get JWT
  request({
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: {
      payload: {
        foo: 'bar'
      }
    },
    uri: `http://localhost:${fastify.server.address().port}/sign`,
    json: true
  }, function (err, response, body) {
    if (err) fastify.log.error(err)
    fastify.log.info(`JWT token is ${body.token}`)

    // verify JWT
    request({
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
        authorization: 'Bearer ' + body.token
      },
      uri: 'http://localhost:' + fastify.server.address().port + '/verify',
      json: true
    }, function (err, response, body) {
      if (err) fastify.log.error(err)
      fastify.log.info(`JWT verified. Foo is ${body.foo}`)
    })
  })
})

Verifying with JWKS

The following example integrates the get-jwks package to fetch a JWKS and verify a JWT against a valid public JWK.

Example
const Fastify = require('fastify')
const fjwt = require('@fastify/jwt')
const buildGetJwks = require('get-jwks')

const fastify = Fastify()
const getJwks = buildGetJwks()

fastify.register(fjwt, {
  decode: { complete: true },
  secret: (request, token) => {
    const { header: { kid, alg }, payload: { iss } } = token
    return getJwks.getPublicKey({ kid, domain: iss, alg })
  }
})

fastify.addHook('onRequest', async (request, reply) => {
  try {
    await request.jwtVerify()
  } catch (err) {
    reply.send(err)
  }
})

fastify.listen({ port: 3000 })

TypeScript

This plugin has two available exports, the default plugin function fastifyJwt and the plugin options object FastifyJWTOptions.

Import them like so:

import fastifyJwt, { FastifyJWTOptions } from '@fastify/jwt'

Define custom Payload Type and Attached User Type to request object

typescript declaration merging

// fastify-jwt.d.ts
import "@fastify/jwt"

declare module "@fastify/jwt" {
  interface FastifyJWT {
    payload: { id: number } // payload type is used for signing and verifying
    user: {
      id: number,
      name: string,
      age: number
      } // user type is return type of `request.user` object
  }
}

// index.ts
fastify.get('/', async (request, reply) => {
  request.user.name // string

  const token = await reply.jwtSign({
    id: '123'
    // ^ Type 'string' is not assignable to type 'number'.
  });
})

Acknowledgements

This project is kindly sponsored by:

License

Licensed under MIT.

fastify-jwt's People

Contributors

cberescu avatar cemremengu avatar climba03003 avatar dancastillo avatar darkgl0w avatar daveamayombo avatar delvedor avatar dependabot-preview[bot] avatar dependabot[bot] avatar eomm avatar ethan-arrowood avatar fdawgs avatar fishorbear avatar github-actions[bot] avatar goto-bus-stop avatar greenkeeper[bot] avatar jsumners avatar kamikazechaser avatar lependu avatar lwojcik avatar mandaputtra avatar maslennikov avatar mattiapv avatar mcollina avatar michieldemey avatar niekheezemans avatar salmanm avatar shogunpanda avatar simoneb avatar uzlopak 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

fastify-jwt's Issues

Consider improve trusted documentation

Hello! I'm a bit confused about trusted tokens signature (request, decodedToken) => true|false|Promise<true|false>. Trying to make it work, I realized that the return of this function is setted as request.user if it isn't an falseable value. Otherwise, if falseable, the token is blacklisted. So, wouldn't it be better if it was more documented expliciting that the returned value is assigned torequest.user object? I wrote some code I'd like to share to explain the situation. Thanks!

server.register(fastifyJwt, {
          secret: process.env.SERVER_JWT_SECRET,
          trusted: (request, decodedToken) =>
            !blacklist.some(
              blacklistedToken => blacklistedToken.jti == decodedToken.jti
            )
              ? (decodedToken as any).payload
              : false
        });

Feature proposal: better options management

🚀 Feature Proposal

A way to manage fastify-jwt "global" default options.
Since v0.5.0 an options key has been introduced, this options key allows users to define some global options to use with fastify-jwt and thus introduced some unwanted side effects due to the way it has been implemented.

With this proposal I would like to introduce an options key like this:

{
  options: {
    decode?: {
      complete?: boolean,
      json?: bool
    },
    sign?: {
      algorithm?: string,
      keyid?: string,
      expiresIn?: string | number,
      notBefore?: string | number,
      audience?: string | string[],
      subject?: string,
      issuer?: string,
      jwtid?: string,
      noTimestamp?: boolean,
      header?: object,
      encoding?: string
    },
    verify?: {
      algorithms?: string[],
      audience?: string | string[],
      clockTimestamp?: number,
      clockTolerance?: number,
      issuer?: string | string[],
      ignoreExpiration?: boolean,
      ignoreNotBefore?: boolean,
      jwtid?: string,
      subject?: string,
      maxAge?: string // Deprecated but still accepted by jsonwebtoken package
    }
  }
}

In the meantime I would like to introduce a new decoration named options to give users an easy way to access those default global options by calling fastify.jwt.options or more specifically fastify.jwt.options.decode, fastify.jwt.options.sign or fastify.jwt.options.verify(given that they have been defined or not).

And those global default options can be overridden by providing 'local' options when the user needs to (check the examples below)

NB: To avoid any breaking change with the v0.5.0 that introduced an options key that took the shape options: { algorithm?: string } (this is the shape that is explicitly documented and thus the one susceptible to be already adopted by the users) I can take into account an extra key algorithm?: string inside of the options and then merge it inside the sign default options. We would need to mark it as deprecated and encourage users to adopt the shape that come with this proposal and then remove that deprecated practice in the version 0.8.0 of the package (if we consider this feature is good to land with the 0.7.0 version of the package). To be honest this is not really needed as I don't think there is too much applications that are concerned, but still it's better to do it this away and let users the time to change their code.

Motivation

Currently managing "global" default options within the package can be a bit confusing, because there is not really any actual filtering on the different accepted options and no real means to define global options for the different methods of the package.
This proposal aims to make the use of fastify-jwt more intuitive by introducing a global options management within the package.

What do you think of those changes ? They could finalize the fastify-jwt wrapping of jsonwebtoken in an almost perfect way.

I have a PR that's almost ready (I already refactored the code and made it more easy to read alongside this feature implementation), all it needs to be complete now is a good documentation rework and a little refactoring of the tests to make them more readable (and i need to add a few more to achieve a better coverage).

I would also like to add a typescript definition file for the package (it can be a good way to avoid a misuse of the package) but I would definitely need some help for this (I mostly use ES only and I am not particularly familiar with Typescript, maybe there is an example typescript definition file for a fastify module that's considered very clean and well implemented - the one I found in the fastify-mongodb plugin repository should be my model maybe ?)

Example

'use strict'

require('make-promises-safe')
const { readFileSync } = require('fs')
const fastify = require('fastify')({
  logger: true
})

fastify.register(require('fastify-jwt'), {
  secret: {
    private: readFileSync('path/to/private/cert.pem'),
    public: readFileSync('path/to/public/cert.pem')
  },
  options: {
    decode: {
      complete: true
    },
    sign: {
      issuer: 'foo'
    },
    verify: {
      issuer: 'foo'
    },
    algorithm: 'RS256' // Deprecated but still valid to avoid any break in the code
  }
})

fastify.get('/', async (request, reply) => {
  try {
    // We use the default global options to generate our token
    const token = await reply.jwtSign({ foo: 'bar' })

    // return a token corresponding to the payload { foo: 'bar', iss: 'foo' }
    return { token }
  } catch (error) {
    reply.code(500)
    return { error }
  }
})

fastify.get('/verify', async (request, reply) => {
  try {
    // We use the default global options to verify the Auth bearer token
    const payload = await request.jwtVerify()

    // { payload: { "foo": "bar", "iat": 1540294345, "iss": "foo" } }
    return { payload }
  } catch (error) {
    reply.code(401)
    return { error }
  }
})

fastify.get('/signAlt', async (request, reply) => {
  try {
    // We clone the global sign options
    // localSignOptions = { algorithm: 'RS256', issuer: 'foo' }
    let localSignOptions = Object.assign({}, fastify.jwt.options.sign )

    // We change the global sign options as we need something
    // that's a little bit different
    // localSignOptions = { algorithm: 'RS256', issuer: 'bar' }
    localSignOptions.issuer = 'bar'
    // localSignOptions = { algorithm: 'RS256', issuer: 'bar', subject: 'test' }
    localSignOptions.subject = 'test'

    // We clone the global verify options
    // localVerifyOptions = { issuer: 'foo' }
    let localVerifyOptions = Object.assign({}, fastify.jwt.options.verify )

    // We change the global sign options as we need something that's a little bit different
    // localVerifyOptions = { issuer: 'bar' }
    localVerifyOptions.issuer = 'bar'
    // localVerifyOptions = { issuer: 'bar', subject: 'test' }
    localVerifyOptions.subject = 'test'

    // We override global signing options with the ones we defined
    // this will generate a token corresponding to the payload { foo: 'bar', iss: 'bar', sub: 'test' }
    const token = await reply.jwtSign({ foo: 'bar' }, localSignOptions)

    // We override our global verification options to match our local signing
    // options and thus avoid a jwt validation error
    const payload = await fastify.jwt.verify(token, localVerifyOptions)

    // { "payload": { "foo": "bar", "iat": 1540305337, "iss": "bar","sub": "test" } }
    return { payload }
  } catch (error) {
    reply.code(401)
    return { error }
  }
})

fastify.get('/decode', async (request, reply) => {
  try {
    // We generate a token using the global default signing options
    const token = await fastify.jwt.sign({ foo: 'bar' })

    // We use the global default decoding options
    const fullyDecodedToken = await fastify.jwt.decode(token)

    // We override the global decoding options
    const noComplete = await fastify.jwt.decode(token, { complete: false })

    /*
    {
      "fullyDecodedToken": {
        "header": {
          "alg": "RS256",
          "typ": "JWT"
        },
        "payload": {
          "foo": "bar",
          "iat": 1540305600,
          "iss": "foo"
        },
        "signature": "gVf5bzROYB4nPgQC0nbJTWCiJ3Ya51cyuP-N50cidYo"
      },
      "noComplete": {
        "foo": "bar",
        "iat": 1540305600,
        "iss": "foo"
      }
    }
    */
    return { fullyDecodedToken, noComplete }
  } catch (error) {
    reply.code(500)
    return { error }
  }
})

fastify.listen(1337, (err) => {
  if (err) throw err
})

100% code coverage

This should be straightforward to bring to 100% code coverage

Time:     2s
----------|----------|----------|----------|----------|-------------------|
File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files |    99.24 |    98.15 |      100 |    99.22 |                   |
 jwt.js   |    99.24 |    98.15 |      100 |    99.22 |               264 |
----------|----------|----------|----------|----------|-------------------|

Potential misinformation on cookies

From the documentation:

Example using cookie

Storing JWT in Cookie mean that your app maybe vunerable to XSS attack and must be protected with CSRF token, consider that as a best practice...

Cookies with the httpOnly cannot be susceptible to XSS on popular browsers. You can prevent CSRF with the sameSite flag which is now supported on popular browsers. So a combination of properly configured httpOnly, sameSite and secure cookies make them almost bullet proof.

..but storing JWT on cookies makes your REST API arent Stateless anymore...

This explicitly assumes the use of sessions which is not always the case.

Http error codes

  • if the authorisation header is missing this will result in a 500 - it should be a 401 not authhorized
  • if the token is invalid this also results in a 500 - this should be a 400 bad request

Missing request decorator

This plugin is adding a .user property to the request object without using a request decorator. As result, the overall performance of the application will suffer.
Luckily the fix is simple: add a request decorator.

fastify.decorateRequest('user', null)

typescript and http2

It seems that this plugin does not work with a server declared as http2secureserver:

fastify.FastifyInstance<
Http2SecureServer,
Http2ServerRequest,
Http2ServerResponse

It it possible to change the typing so that jwt support both http and http2 server?

I'm not sure where the problem is

Hello!
I need help with the implementation of the plugin. It doesn't work as intended. Probably because I didn't understand something. I'm a novice.
I did read the Docs and about encapsulation and it seems good to me. But it doesn't. So, it's sure i misunderstood something.

I have this code:

server.js
fastify.register(require('./routes/users'))

routes/users.js

module.exports = async function (fastify, options) {
  fastify.register(require('../decors/users'))
  console.log(fastify.authenticate)  // this is undefined

  fastify.route({
    method: 'POST',
    url: '/users',
    schema: {
      body: {
        type: 'object',
        required: ['username', 'email', 'password'],
        properties: {
          email: { type: 'string' },
          password: { type: 'string' },
          username: { type: 'string' }
        }
      },
      response: {
        201: {
          type: 'object',
          properties: {
            email: { type: 'string' },
            username: { type: 'string' }
          }
        }
      }
    },
    preHandler: [fastify.authenticate],
    handler: fastify.addUser
  })
}

decors/users

const fp = require('fastify-plugin')

module.exports = fp(async function (fastify, options) {
  fastify.register(require('fastify-jwt'), {
    secret: 'supersecret'
  })

  fastify.decorate('authenticate', async function(request, reply) {
    try {
      await request.jwtVerify()
    } catch (err) {
      reply.send(err)
    }
  })

  fastify.decorate('addUser', async function(request, reply) {
    // something
  })
})

I'm getting this error: Missing handler function for POST:/users route.
And that's because fastify.authenticate and fastify.addUser are undefined.
But I've registered them, no? 😕

I hope this is the place to ask for help.
Thanks in advance!

Allow custom unauthorized messages

At the moment the library is using the following hard coded messages:

'No Authorization was found in request.headers'
'Authorization token is invalid: ' + err.message
'Authorization token expired'

Allow users to over ride these with their custom messages, otherwise the library can fallback to the defaults above.

Add pre-commit as devDependencies

🚀 Feature Proposal

add pre-commit as devDependencies

Motivation

It can ensure code is well tested before committing and pushing to git.

Example

Please provide an example for how this feature would be used.

alllow secret to return a Promise

🚀 Feature Proposal

Here is an example:

const fastify = require('fastify')()
const jwt = require('fastify-jwt')
const request = require('request')

fastify.register(jwt, {
  secret: async function (request, reply) {
    // do something async
    return 'supersecret'
  }
})

fastify.jwt undefined

This code:

const fastify = require('fastify')();
fastify.register(require('fastify-jwt'), {
    secret: 'supersecret'
});
console.log('fastify.jwt', fastify.jwt);

prints:

fastify.jwt undefined

Any idea why?

An in-range update of jsonwebtoken is breaking the build 🚨

☝️ Greenkeeper’s updated Terms of Service will come into effect on April 6th, 2018.

Version 8.2.0 of jsonwebtoken was just published.

Branch Build failing 🚨
Dependency jsonwebtoken
Current Version 8.1.1
Type dependency

This version is covered by your current version range and after updating it in your project the build failed.

jsonwebtoken is a direct dependency of this project, and it is very likely causing it to break. If other packages depend on yours, this update is probably also breaking those in turn.

Status Details
  • continuous-integration/travis-ci/push The Travis CI build could not complete due to an error Details

Commits

The new version differs by 3 commits.

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Authorization token is invalid: invalid signature

🐛 Bug Report

when I use java client create jwt,request the node server,throw a exception:Authorization token is invalid: invalid signature

To Reproduce

java client example token:

eyJhbGciOiJIUzI1NiJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE2MDY4MDQyOTR9.BhOclXXW5bY2_pJQlKmfjBzsdGPl7rY-Z9g3l3FQylo

node server code

const fastify = require('fastify')({logger:true})
const jwt = require('fastify-jwt')

fastify.register(jwt, {
    secret: "test"
})

fastify.post('/sign', async function (request, reply) {
    const token = await reply.jwtSign(request.body)
    reply.send({ token: token })
})

fastify.get('/verify',
    function (request, reply) {
    request.jwtVerify(function (err, decoded) {
        return reply.send(err || decoded)
    })
})

fastify.get('/hello', function (request, reply) {
    reply.send({ hello: "hello" })
})

fastify.addHook('onRequest', async (request, reply) => {
    try {
        console.log(request.url)
        var jwtVerify = await request.jwtVerify();
        console.log(jwtVerify)
    } catch (err) {
        reply.send(err)
    }
})


fastify.listen(3000, function (err) {
    if (err) fastify.log.error(err)
    fastify.log.info(`Server live on port: ${fastify.server.address().port}`)
})

Expected behavior

I want to know how to use success , thanks

Your Environment

  • node version: v14.4.0
  • fastify version: >=2.0.0
  • os: Mac, Windows, Linux
  • any other relevant information

versions on npm

The versions on npm is misleading on here
Currently it says it has version 2.1.3 , and 1.5.0 and the current versions are 2.1.2 , and 1.5.0.

Also 1.5.0 has issue with fastify v3 to build using TypeScript, but 2.1.3 works.

Beside on the master branch the package.json says version: "2.1.3"

When I run npm outdated, I get the warning fastify-jwt 2.1.3 2.1.3 1.5.0 which means that the most recent version is 1.5.0

Can any one please explain what is happening.

JWT in reply header or cookie?

Fastify being an opinionated web framework may I ask for your opinion on how to use JWT?

I just switched from sessions to JWT. This was easily done by storing the JWT as a cookie which I sent with the reply from the authorization middleware.

I set cookie with a reply.redirect('/') from the login route.

The fastify-jwt documentation shows how to put the token as bearer in the Authentication header. How would I use this with reply.redirects()?

Is there a smart way to set the header for all calls from a client application or would I have to manually change every xhr request?

Given that the cookie method works well are there any obvious downsides which I am not seeing?

fastify.jwt is undefined?

I am trying to use the fastify-jwt package, although it is coming back undefined?

Dependency versions:

"fastify": "^3.8.0",
 "fastify-jwt": "^2.1.3",

index.js:

const fastify = require('fastify')()
const routes = require('./routes')

fastify.register(routes);

module.exports = fastify

server.js:

'use strict'

require('dotenv').config();

const fastify = require('./index')

fastify.register(require('fastify-jwt'), {
  secret: process.env.JWT_SECRET
});

fastify.listen(process.env.SERVER_PORT, () =>
  console.log(`Server listening on port ${process.env.SERVER_PORT}!`),
);

loginController.js:

const userModel = require('../models/userModel');
const fastify = require('fastify');

async function handleLoginRequest(request, reply) {
    if (!request.body.email || !request.body.password) {
        reply.send({
            'error': true,
            'error_message': 'Some mandatory parameters in the request body are missing.'
        })
    };

    var correctLogin = await userModel.validateLogin(request.body.email, request.body.password);

    if (correctLogin) {
        let payload = request.body;
        console.log(fastify.jwt);
        reply.send({
            'access_token': fastify.jwt.sign({ payload })
        });
    }
    else {
        reply.send({
            'response_text': 'Incorrect login details, please try again.'
        });
    }
}

async function handleIncorrectLogin(request, reply) {
}

module.exports = {
    handleLoginRequest
}

I start my app using
node server

Verifying a JWT from a signed cookie

I'm currently using both fastify-jwt and fastify-cookie to store my access token.

app.register(jwt, {
  secret: JWT_SECRET,
  cookie: {
    cookieName: COOKIE_ACCESS_TOKEN,
  },
});

app.register(cookie, {
  secret: COOKIE_SECRET,
});

The one caveat is that the cookie that I'm storing is signed. The main issue with that is that I can't use the following snippet to verify the JWT token:

request.jwtVerify()

because it will always be a malformed JWT token. I need to verify the unsigned version. So I had to imeplement my own verification function:

/**
  * Verify JWT Access Token
  */
const verifyAccessToken = async () => {
  const accessToken = request.cookies[COOKIE_ACCESS_TOKEN];
  const unsignedAccessToken = request.unsignCookie(accessToken);

  if (!unsignedAccessToken.value) {
    return null;
  }

  return app.jwt.verify(unsignedAccessToken.value);
};

That works great, but I really want to be able to use the jwtVerify() helper so that request.user gets populated from the decorateRequest API.

What do you guys think about planning for that in the verify logic? Maybe when registering the plugin, we could pass in a boolean to indicate if the cookie is signed or not:

cookie: {
  cookieName: COOKIE_ACCESS_TOKEN,
  signed: true
},

Has anyone run into this before?

Edit: I just created a PR to demonstrate what I had in mind. Let me know what you guys think: #152

Problems with RSA support


name: 🐛 Bug report
about: Problems with RSA support

🐛 Bug Report

RSA support does not work as intended, and there are errors in the test file that lead to a bias in validating tests that shouldn't pass in the actual state of the code (Currently that's a supposition, i must dig further in the code to see if my assumptions are right concerning the tests).

To Reproduce

Steps to reproduce the behavior:

create an index.js file like this (sorry that's not the best code but that's enough to reproduce the behavior):

'use strict'

// We make promises safe.
require('make-promises-safe');

const {readFileSync} = require('fs')
const Fastify = require('fastify')

const certificates = {
  public: readFileSync('certs/public.key'),
  private: readFileSync('certs/private.key')
}

const fastify = Fastify()

fastify.register(require('fastify-jwt'), {
  secret: {
    key: certificates.private,
    passphrase: certificates.public
  },
  algorithm: 'RS256'
})

fastify.post('/token', async (request, reply) => {
  // we just return a basic token
  const payload = { foo: 'bar' }

  let token = await fastify.jwt.sign(payload)

  return { token }
})

fastify.get('/verifyOk', async (request, reply) => {
  try {
    // Should return the payload { foo: 'bar' }
    // if requested with a Bearer Auth valid token
    let token;

    if (request.headers && request.headers.authorization) {
      let parts = request.headers.authorization.split(' ')
      if (parts.length === 2) {
        token = parts[1]

        if (parts[0] !== 'Bearer') {
          throw new Error('Format is Authorization: Bearer [token]')
        }
      }
    } else {
      throw new Error('No Authorization was found in request.headers')
    }

    let decoded = await fastify.jwt.verify(token)

    return { decoded }
  } catch (error) {
    reply.code(401)
    return { error }
  }
})

fastify.get('/verifyFail', async (request, reply) => {
  // Should return the payload { foo: 'bar' }
  // if requested with a Bearer Auth valid token
  // But instead it return a 'JsonWebTokenError: invalid algorithm' error
  try {
    let decoded = await request.jwtVerify()

    return { decoded }
  } catch (error) {
    reply.code(401)

    return { error }
  }
})

fastify.listen(3000, err => {
  if (err) throw err
})

NB.: I used the fastify-jwt certs file (you can get the public and private key from the repo).

Expected behavior

I would expect that the requests to localhost:3000/verifyOk and to localhost:3000/verifyFail would return the same thing but that's not the case :(

When using request.jwtVerify() fastify-jwt always return an Invalid algorithm error.

screen_1539703464

screen_1539703631

Your Environment

  • node version: 10.12.0
  • fastify version: 1.12.1
  • os: Mac (Mojave), Windows 10 Pro N (build 1809), Linux (Archlinux)
  • fastify-jwt version: 0.5.0

Build with typescript compiler error

🐛 Bug Report

Build error #96
Error: Namespace fastify/fastify has no exported member 'JWTTypes'.

To Reproduce

Steps to reproduce the behavior:
clone https://github.com/ekoeryanto/TypeScript-Fastify-API/tree/repro

git clone --depth=1 --single-branch --branch repro https://github.com/ekoeryanto/TypeScript-Fastify-API.git jwt-repro
cd jwt-repro
npm i
npm run build

Expected behavior

built

Your Environment

  • node version: 10, 12, 13
  • fastify version: next
  • os: Windows, Linux
  • any other relevant information

I use expiredIn to set a time to expire ,but when i use 14d ,it change the month but not change the days.

🐛 Bug Report

please tell me how to use the expiredIn.
when i set 14d it return a wrong exp which month has been changed.

To Reproduce

  sign:{
      expiresIn:"14d",
      notBefore:"0"
    },

Wrong exp Result

result
"iat": 1609811045,
"nbf": 1609811045,
"exp": 1611020645,

i use dayjs to change them :

const now = Date.parse(new Date())
      console.log('XXXXXXXXXXXXXXXXXXXXX',dayjs(now).format("YYYY-DD-MM HH:mm:ss"))
      console.log('XXXXXXXXXXXXXXXXXXXXX',dayjs.unix(iat).format("YYYY-DD-MM HH:mm:ss"))
      console.log('XXXXXXXXXXXXXXXXXXXXX',dayjs.unix(exp).format("YYYY-DD-MM HH:mm:ss"))
XXXXXXXXXXXXXXXXXXXXX 2021-05-01 10:45:57
XXXXXXXXXXXXXXXXXXXXX 2021-05-01 10:45:48
XXXXXXXXXXXXXXXXXXXXX 2021-06-01 10:45:48  //i just want to change the day but not the month

Your Environment

  • node version: latest
  • fastify version: >=3.7.0
  • os: Windows
  • any other relevant information

Consider allowing claims object to be formatted.

🚀 Feature Proposal

Currently, the claims object obtained by decoding the token is added as request.user directly. An option to format the claims object before could be useful.

Motivation

The most useful representation of the data in the token is not necessarily the way it was encoded. Claims may be given names that are abbreviated ("dept_name"), misspelled ("depatment_name"), or confusing ("belongs_to"). Or, maybe it'd just be preferable to have request.user.departmentName over request.user.department_name. While this can be handled by following up with overwriting request.user with a formatted version

const fastify = require('fastify')();
fastify.register(require('fastify-jwt'), {
  secret: 'supersecret'
});

fastify.addHook('onRequest', async (request, reply) => {
  try {
    await request.jwtVerify();
    request.user = {
      departmentName: request.user.department_name,
      name: request.user.name
    };
  } catch (err) {
    reply.send(err);
  }
});

it isn't an elegant solution.

Example

Ideally, it'd just be an option.

const fastify = require('fastify')();
fastify.register(require('fastify-jwt'), {
  // Takes the claims object and returns a formatted version.
  formatUser: user => ({
    departmentName: user.department_name
    name: user.name
  }),
  secret: 'supersecret'
});

fastify.addHook('onRequest', async (request, reply) => {
  try {
    await request.jwtVerify();
  } catch (err) {
    reply.send(err);
  }
});

fastify.get("/", async (request, reply) => {
  // `request.user` is the object returned by `formatUser`.
  return `Hello, ${request.user.name} from ${request.user.departmentName}.`;
});

Action required: Greenkeeper could not be activated 🚨

🚨 You need to enable Continuous Integration on all branches of this repository. 🚨

To enable Greenkeeper, you need to make sure that a commit status is reported on all branches. This is required by Greenkeeper because we are using your CI build statuses to figure out when to notify you about breaking changes.

Since we did not receive a CI status on the greenkeeper/initial branch, we assume that you still need to configure it.

If you have already set up a CI for this repository, you might need to check your configuration. Make sure it will run on all new branches. If you don’t want it to run on every branch, you can whitelist branches starting with greenkeeper/.

We recommend using Travis CI, but Greenkeeper will work with every other CI service as well.

Fastify instance is not decorated

Hello guys,

I tried to implement JWT into my code but I can't access the methods, because fastify.jwt is undefined. If I try to user fastify.jwt.sign() I receive an error: Cannot read property 'sign' of undefined.

const fastify = require('fastify')()
fastify.register(require('fastify-jwt'), {
    secret: 'SECRETSECRETSECRETSECRETSECRETSECRETSECRET'
})
async (req, res, next) => {
    const token = await fastify.JWT.sign({ user: 1 })
    await res.send(token);
}

I appreciate your help.
Best

Typescript: FastifyJWTOptions.secret function type missing

🐛 Bug Report

In documentation said that secret The secret can be a primitive type String, a function that returns a String or an object { private, public }

But in type definition file the secret type is jwt.Secret | { public: jwt.Secret; private: jwt.Secret }

To Reproduce

Steps to reproduce the behavior:

  • build the following TS code
const app = fastify()
    .register(jwtPlugin, { secret: () => 'secret' })

I got following error:

Type '() => string' is not assignable to type 'string | Buffer | { key: string | Buffer; passphrase: string; } | { public: Secret; private: Secret; }'.

Expected behaviour

There is no build errors

Your Environment

  • node version: 10, 12, 13, 14
  • fastify version: >=2.0.0
  • os: Mac, Windows, Linux
  • any other relevant information

Does not handle dynamic secrets

As far as I can tell, fastify-jwt does not support callback functions as the secret. This behavior is supported by express-jwt so I think it should be supported here too.

I'm going to attempt to add this functionality!

Doesn't work when deconstructing or passing `jwtSign` to another function

I'm getting the following error when I deconstruct jwtSign from reply or pass it to another function:

TypeError: Cannot read property 'jwtSign' of undefined
    at path\node_modules\fastify-jwt\jwt.js:148:15
    at new Promise (<anonymous>)
    at replySign (path\node_modules\fastify-jwt\jwt.js:147:14)
    at Object.postLogin (path\services\user\local.js:15:23)
    at process._tickCallback (internal/process/next_tick.js:68:7)

My service:

fastify.post('/login', async function ({ body }, { jwtSign }) => {
  try {
    console.log(await jwtSign({ email: body.email }))
  } catch (err) {
    console.error(err)
  }
})

Same for:

async function sign(jwtSign, email) {
  return jwtSign({ email })
}

fastify.post('/login', async function ({ body }, reply) => {
  try {
    console.log(await sign(reply.jwtSign, body.email))
  } catch (err) {
    console.error(err)
  }
})

It does work when I just use reply.jwtSign() directly though.

Consumed fastify-jwt module is missing jsonwebtoken types

In a basic typescript project:
server.ts:

import * as Fastify from 'fastify'
import * as fastifyJwt from 'fastify-jwt'

const fastify = Fastify()

fastify.register(fastifyJwt, {
  secret: 'supersecret'
})

fastify.post('/signup', (req, reply) => {
  // some code
  const token = fastify.jwt.sign({ "foo":"bar" })
  reply.send({ token })
})

fastify.listen(3000, err => {
  if (err) throw err
})

tsconfig.json:

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es6",
    "noEmit": true,
    "strict": true,
    "noImplicitAny": true
  },
  "include": [
    "server.ts"
  ],
  "exclude": [
    "node_modules"
  ]
}

Running this basic build command results in the following error:

👻 tsc-fastify-playground▷ npm run build

> [email protected] build /Users/ethanarrowood/Documents/GitHub/Fastify/tsc-fastify-playground
> tsc --project tsconfig.json

node_modules/fastify-jwt/index.d.ts:3:83 - error TS7016: Could not find a declaration file for module 'jsonwebtoken'. '/Users/ethanarrowood/Documents/GitHub/Fastify/tsc-fastify-playground/node_modules/jsonwebtoken/index.js' implicitly has an 'any' type.
  Try `npm install @types/jsonwebtoken` if it exists or add a new declaration (.d.ts) file containing `declare module 'jsonwebtoken';`

3 import { DecodeOptions, Secret, SignOptions, VerifyCallback, VerifyOptions } from "jsonwebtoken";
                                                                                    ~~~~~~~~~~~~~~


Found 1 error.

npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] build: `tsc --project tsconfig.json`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the [email protected] build script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/ethanarrowood/.npm/_logs/2019-03-28T17_13_55_699Z-debug.log

Not sure whats up -- going to work on it and figure it out though

Customize how JWT is received from headers

🚀 Feature Proposal

Right now we are forced to use the header format Authorization: Bearer [token]. It would be nice if we could customize the function used to fetch the [token] so we could remove the Bearer part, or use a different header

Motivation

Flexibility in usage

Example

server.addHook("onRequest", async (request, reply) => {
    try {
      await request.jwtVerify({
        vverifyFunc: (request, reply) => {
          return request.headers['Auth']
        }
      })
    } catch (err) {
      reply.send(err)
    }
  })

Using fastify-jwt along with Auth0

Hello,

I have used fastify-jwt in the past where my servers were in charge of issuing JWT tokens, etc. However, I am now trying to use Auth0 as the authority. I want to use Fastify as my server and verify tokens sent from clients and devices that have previously authenticate using Auth0 directly.

My question/request is how is this achieved using fastify-jwt? I have seen several other node packages but all based on the premise of using Express. I don't want to use Express and want to have everything working the "Fastify" way. Would it be possible to provide a working repo that uses RS256 and allow us to mark up our endpoints using preValidation: [fastify.authenticate]?

Thanks in advanced!

NPM latest tag is v1

🐛 Bug Report

A clear and concise description of what the bug is.

To Reproduce

Steps to reproduce the behavior:

// Paste your code here

Expected behavior

A clear and concise description of what you expected to happen.

// Paste the expected results here

Your Environment

  • node version: 10, 12, 13
  • fastify version: >=2.0.0
  • os: Mac, Windows, Linux
  • any other relevant information

Typescript fails to compile the plugin

🐛 Bug Report

Application that uses [email protected] fails to compile with typescript.

To Reproduce

Steps to reproduce the behavior:

  1. Install and import the module.
  2. Try to compile:
npx tsc
node_modules/fastify-jwt/jwt.d.ts:77:123 - error TS2694: Namespace '"node_modules/fastify/fastify"' has no exported member 'JWTTypes'.

...

Found 2 errors.

Your Environment

  • node version: v14.2.0
  • typescript version: 3.9.5
  • fastify version: 3.0.0-rc.3
  • os: Mac

Named imports do not work with TypeScript

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure it has not already been reported

Fastify version

3.x

Node.js version

14.x

Operating system

Windows

Operating system version (i.e. 20.04, 11.3, 10)

10

Description

Despite TS types claiming that this import is fine:

import { fastifyJWT } from 'fastify-jwt'

app.register(fastifyJWT, {
  secret: secret
})

, it actually isn't and throws an error that not a plugin is being registered.

Steps to Reproduce

Try to import plugin using named import in TypeScript

Expected Behavior

Named import should work.

typescript secret with public/private keys

At https://github.com/fastify/fastify-jwt#example we have:

fastify.register(jwt, {
  secret: {
    private: readFileSync(`${path.join(__dirname, 'certs')}/private.key`, 'utf8'),
    public: readFileSync(`${path.join(__dirname, 'certs')}/public.key`, 'utf8')
  },
  sign: { algorithm: 'RS256' }
})

However at https://github.com/fastify/fastify-jwt/blob/master/index.d.ts#L62 we have:

declare interface FastifyJWTOptions {
  secret: jwt.Secret;
  decode?: jwt.DecodeOptions;
  sign?: jwt.SignOptions;
  verify?: jwt.VerifyOptions;
}

But the jwt.Secret is defined as https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/jsonwebtoken/index.d.ts#L111

export type Secret = string | Buffer | { key: string; passphrase: string };

Please change your definition to something that allows your suggested structure:

declare interface FastifyJWTOptions {
  secret:
    | jwt.Secret
    | {
        public: jwt.Secret;
        private: jwt.Secret;
      };
  decode?: jwt.DecodeOptions;
  sign?: jwt.SignOptions;
  verify?: jwt.VerifyOptions;
}

Add ability to specify the token location in request headers

🚀 Feature Proposal

Currently the default, non-configurable behavior for tokens sent with headers (meaning, not in cookies) is hardcoded to look for the token in the Authorization header with format Bearer {token}. It should be possible to specify a custom location and format for the token in the headers. For instance, a custom header, and a custom format not necessarily involving Bearer.

Motivation

JWT doesn't prescribe a way to send a token to an API, so it should be possible to provide a custom location where the token is retrieved from and validated.

Example

The custom logic should override these lines of code, and could be provided as an option to the plugin via a callback which is used to extract the token from the request, for instance:

extractToken: request => token

cant decode token and retrieve username

As reported here:

https://stackoverflow.com/questions/49831398/retrieve-username-from-jwt-token-using-fastify

I am able to create a jwt token:

fastify.post('/signup', (req, reply) => {
  const token = fastify.jwt.sign({
    payload,
  })
  reply.send({ token })
})

that can return something like:

{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MjM3MDgyMzF9.HZqqiL7wwPaEQihUGoF7Y42Ia67HgKJ-1Ms38Nvcsmw"}

but if I try to decode the username frmo token

fastify.get('/decode', async (request, reply) => {
  const auth = request.headers.authorization;
  const token = auth.split(' ')[1]
  fastify.jwt.verify(token, (err, decoded) => {
    if (err) fastify.log.error(err)
    fastify.log.info('username : ' + decoded.username)
    reply.send({
      foo: decoded,
    })
  })
})

the response is:

{"foo":{"iat":1523660987}}

reply.send is not a function

After adding the hook:

// Add hook to perform JWT verification
fastify.addHook("onRequest", async (request, reply) => {
  try {
    await request.jwtVerify()
  } catch (err) {
    reply.send(err)
  }
})

Received:

{"statusCode":500,"error":"Internal Server Error","message":"reply.send is not a function"}

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.