@fastify/jwt

repository·main·Indexed 20 days ago

https://github.com/fastify/fastify-jwt

JWT (JSON Web Token) utilities for Fastify, powered by fast-jwt. Provides methods for signing, decoding, and verifying tokens, including support for global verification via onRequest hooks, cookie-based token storage, asymmetric algorithms (RSA, ECDSA), and token blacklisting via the trusted option. Version 10.2.1.

Tokens
9.5K
Snippets
30
Records
41
Agent score
69%

What's inside @fastify/jwt

  1. Use multiple JWT validators with `namespace`

    main

    The namespace option allows you to define multiple independent JWT configurations on the same Fastify instance.

    Behavior:

    • fastify.jwt becomes a map of namespaces. For namespace: 'security', you access utilities via fastify.jwt.security.sign(), fastify.jwt.security.verify(), etc.
    • Decorators: The plugin decorates the request object with names derived from the namespace. If you don't provide custom aliases, it uses <namespace>JwtVerify, <namespace>JwtSign, and <namespace>JwtDecode.

    TypeScript Support: Use FastifyJwtNamespace to type the namespaces on the FastifyInstance and declare the namespaces in the FastifyJWT interface.

    // Registering two different JWT configurations
    fastify.register(jwt, {
      secret: 'test',
      namespace: 'security',
      jwtVerify: 'securityVerify',
      jwtSign: 'securitySign'
    })
    
    fastify.register(jwt, {
      secret: 'fastify',
      namespace: 'airDrop'
    })
    
    // Accessing them
    fastify.post('/sign/:namespace', async (request, reply) => {
      if (request.params.namespace === 'security') {
        return reply.securitySign(request.body)
      }
      return reply.airDropJwtSign(request.body)
    })
  2. Verify JWTs globally using onRequest hook

    main

    To verify JWTs for every incoming request in your application, register the plugin and use a global onRequest hook to call request.jwtVerify(). If verification fails, the error should be handled (e.g., by sending it back in the reply). Once verified, the decoded payload is available at request.user.

    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)
      }
    })
    
    // Accessing the user in a route
    fastify.get("/", async function(request, reply) {
      return request.user
    })
  3. Upgrade from @fastify/jwt v3.x to v4.0

    main
    When upgrading from version 3.x to 4.0, note that the underlying library was migrated from jsonwebtoken to fast-jwt. This migration requires updating the option keys used in sign, verify, and decode methods.
  4. Generate RSA and ECDSA certificates via OpenSSL

    main

    Use the following OpenSSL commands to generate the keys required for asymmetric JWT signing.

    RSA (No Passphrase)

    openssl genrsa -out private.key 2048
    openssl rsa -in private.key -out public.key -outform PEM -pubout

    RSA (With Passphrase)

    # Generate encrypted private key
    openssl genrsa -des3 -out private.pem 2048
    
    # Export public key
    openssl rsa -in private.pem -outform PEM -pubout -out public.pem

    ECDSA (No Passphrase)

    # Generate P-256 curve key
    openssl ecparam -genkey -name prime256v1 -out privateECDSA.key
    
    # Export public key
    openssl ec -in privateECDSA.key -pubout -out publicECDSA.key

    ECDSA (With Passphrase)

    # Generate encrypted P-256 curve key
    openssl ecparam -genkey -name prime256v1 | openssl ec -aes256 -out privateECDSA.pem
    
    # Export public key
    openssl ec -in privateECDSA.pem -pubout -out publicECDSA.pem
  5. Use JWTs with cookies

    main

    To store tokens in cookies (improving XSS protection via httpOnly and secure flags), configure the cookie option.

    Requirements & Behavior:

    • You must register @fastify/cookie alongside @fastify/jwt.
    • The plugin looks for a decorated request.cookies property.
    • Fallback: If the request contains both an Authorization header and a cookie, or if the cookie is empty but the header is present, the plugin falls back to the Authorization header.
    • Signing: If you are signing your cookie, set signed: true to ensure 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' })
      reply.setCookie('token', token, { httpOnly: true, secure: true })
      return 'Cookie sent'
    })
  6. Register @fastify/jwt and use sign/decode/verify

    main

    Register @fastify/jwt as a plugin. You must provide a secret in the registration options. Once registered, the plugin decorates the fastify instance with decode, sign, and verify methods. It also adds request.jwtVerify and reply.jwtSign to the request and reply objects respectively.

    const fastify = require('fastify')()
    fastify.register(require('@fastify/jwt'), {
      secret: 'supersecret'
    })
    
    fastify.post('/signup', (req, reply) => {
      // some code
      const token = fastify.jwt.sign({ payload: { user: 'example' } })
      reply.send({ token })
    })
    
    fastify.listen({ port: 3000 }, err => {
      if (err) throw err
    })
  7. Protect specific routes using a custom authentication decorator

    main

    To protect only specific routes rather than the entire application, wrap the @fastify/jwt registration and a custom authentication logic into a fastify-plugin. This allows you to create a reusable decorator (e.g., fastify.authenticate) that can be applied to specific routes via the onRequest option.

    const fp = require("fastify-plugin")
    
    // 1. Create an authentication 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)
        }
      })
    })
    
    // 2. Use the decorator in your routes
    module.exports = async function(fastify, opts) {
      fastify.get(
        "/",
        {
          onRequest: [fastify.authenticate]
        },
        async function(request, reply) {
          return request.user
        }
      )
    }
  8. Configure TypeScript types for Payload and User

    main

    To use TypeScript with @fastify/jwt, import the plugin and the FastifyJWTOptions type. You can use declaration merging to define the shape of your JWT payload and the user object attached to the request.

    // 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
    import fastifyJwt, { FastifyJWTOptions } from '@fastify/jwt'
    
    // Now request.user.name is typed as string
    fastify.get('/', async (request, reply) => {
      request.user.name 
    
      const token = await reply.jwtSign({
        id: 123
      });
    })
  9. How namespaces work in @fastify/jwt

    main

    If you provide a namespace option during registration, the plugin will not decorate fastify.jwt directly. Instead, it will decorate fastify.jwt[namespace].

    When using a namespace, the request and reply decorators are also renamed based on the jwtDecode, jwtVerify, and jwtSign options provided. For example, if namespace: 'auth' and jwtVerify: 'check', the decorator will be request.check().

    Note: If a namespace is used, the decoratorName (the property on request where the user is stored) is still applied to the request object directly.

    // Registration with namespace
    fastify.register(fastifyJwt, {
      namespace: 'auth',
      jwtVerify: 'verifyAuth',
      decoratorName: 'account'
    })
    
    // Usage in route
    fastify.get('/profile', async (request, reply) => {
      await request.verifyAuth()
      return request.account
    })
    
    // Accessing the JWT API
    const token = await fastify.jwt.auth.sign({ id: 1 })
  10. Verify only the cookie using `onlyCookie`

    main

    Setting onlyCookie: true instructs the plugin to decode and verify the token exclusively from the request cookies. This is useful for refreshToken implementations where the main access token is in the header and the refresh token is in a cookie, allowing you to validate the refresh token independently.

    fastify.register(jwt, {
      secret: 'foobar',
      cookie: { cookieName: 'refreshToken' }
    })
    
    // In a hook or route
    fastify.addHook('onRequest', (request) => request.jwtVerify({ onlyCookie: true }))
  11. Configure the `secret` option

    main

    The secret option is required. It defines how tokens are signed and verified. It supports several formats:

    • Primitive: A simple String.
    • Function: An async function, a function returning a Promise, or a function with a callback (request, token, callback). This is useful for dynamic secrets.
    • Object { private, public }: Used for asymmetric algorithms (RSA, ECDSA).
      • private: A string, buffer, or object { key, passphrase } containing the private key.
      • public: A string or buffer containing the public key.

    Verify-only mode: If you only provide a public key in the object (e.g., { public: '...' }), the plugin enters verify-only mode. Decoding and verification will work, but any attempt to use sign functionality will throw an exception.

    // secret as a string
    fastify.register(jwt, { secret: 'supersecret' })
    
    // secret as an async function
    fastify.register(jwt, {
      secret: async function (request, token) {
        return 'supersecret'
      }
    })
    
    // secret as an object of RSA keys
    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' }
    })
    
    // VERIFY-ONLY mode (public key only)
    fastify.register(jwt, {
      secret: {
        public: process.env.JWT_ISSUER_PUBKEY
      }
    })