npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

fastify-api-key

v1.0.2

Published

Fastify plugin to authenticate HTTP requests based on api key and signature

Downloads

1,746

Readme

fastify-api-key

CI js-standard-style

Fastify plugin to authenticate HTTP requests based on api key and signature.

Installation

$ npm install --save fastify-api-key  

Usage

This middleware authenticates callers using an api key and the signature of the request.

Example

This plugin decorates the fastify request with a apiKeyVerify function. You can use a global onRequest hook to define the verification process :

const fastify = require('fastify')()  
const { Unauthorized } = require('http-errors')  
  
const apiKeys = new Map()  
apiKeys.set('123456789', 'secret1')  
apiKeys.set('987654321', 'secret2')  
  
fastify.register(require('fastify-api-key'), {  
  getSecret: (request, keyId, callback) => {  
    const secret = apiKeys.get(keyId)  
    if (!secret) {  
      return callback(Unauthorized('Unknown client'))  
    }  
    callback(null, secret)  
  },  
})  
  
fastify.addHook('onRequest', async (request, reply) => {  
  try {  
    await request.apiKeyVerify()  
  } catch (err) {  
    reply.send(err)  
  }  
})  
  
fastify.listen(3000, (err) => {  
  if (err) throw err  
})

It is possible (and recommanded) to wrap your authentication logic into a plugin :

const fp = require('fastify-plugin')  
  
module.exports = fp(async function (fastify, opts) {  
  fastify.register(require('fastify-api-key'), {  
    getSecret: (request, keyId, callback) => {  
      callback(null, 'secret')  
    },  
  })  
  fastify.decorate('authenticate', async function (request, reply) {  
    try {  
      await request.apiKeyVerify()  
    } catch (err) {  
      reply.send(err)  
    }  
  })  
})

Then use the preValidation of a route to protect it :

module.exports = async function (fastify, opts) {  
  fastify.get(  
    '/', {  
      preValidation: [fastify.authenticate],  
    }, async function (request, reply) {  
      reply.send({ hello: 'world' })  
    })  
}

API

fastifyApiKey(options)

Create an api key based authentication plugin using the given options :

| Name | Type | Default | Description |
| :---------------: | :-------------: | :-------------: | :---------------------------------------------- |
| getSecret | Function | - | Invoked to retrieve the secret from the keyId component of the signature |
| requestLifetime | Number | null | 300 | The lifetime of a request in seconds |

options.getSecret (REQUIRED)

A function with signature function(request, keyId, callback) to be invoked to retrieve the secret from the keyId component of the signature.

  • request (FastifyRequest) - The current fastify request.
  • keyId (String) - The api key used to retrieve the secret.
  • callback (Function) - A function with signature function(err, secret) to be invoked when the secret is retrieved.
    • err (Error) - The error that occurred.
    • secret (String) - The secret to use to verify the signature.
const fastify = require('fastify')()  
const { Unauthorized } = require('http-errors')  
  
const apiKeys = new Map()  
apiKeys.set('123456789', 'secret1')  
apiKeys.set('987654321', 'secret2')  
  
fastify.register(require('fastify-api-key'), {  
  getSecret: (request, keyId, callback) => {  
    const secret = apiKeys.get(keyId)  
    if (!secret) {  
      return callback(Unauthorized('Unknown client'))  
    }  
    callback(null, secret)  
  },  
})

The callback parameter is optional. In the case getSecret must return a promise with the secret value:

const fastify = require('fastify')()  
const { Unauthorized } = require('http-errors')  
  
const apiKeys = new Map()  
apiKeys.set('123456789', 'secret1')  
apiKeys.set('987654321', 'secret2')  
  
fastify.register(require('fastify-api-key'), {  
  getSecret: async (request, keyId) => {  
    const secret = apiKeys.get(keyId)  
    if (!secret) {  
      return callback(Unauthorized('Unknown client'))  
    }  
    return secret  
  },  
})

options.requestLifetime (OPTIONAL)

The lifetime of a request in second, by default is set to 300 seconds, set it to null to disable it. This options is used if HTTP header "date" is used to create the signature.

request.apiKeyVerify(callback)

  • callback (Function) - A function with signature function(err) to be invoked when the secret is retrieved.
    • err (Error) - The error that occurred.
fastify.get('/verify', function (request, reply) {  
  request.apiKeyVerify(function (err) {  
    return reply.send(err || { hello: 'world' })  
  })  
})

The callback parameter is optional. In the case apiKeyVerify return a promise.

fastify.get('/verify', async function (request, reply) {  
  try {  
    await request.apiKeyVerify()  
    reply.send({ hello: 'world' })  
  } catch (err) {  
    reply.send(err)  
  }  
})

HTTP signature scheme

The signature is based on this draft "Signing HTTP Messages". Your application must provide to the client application both unique identifier :

  • key : A key used to identify the client application;
  • shared secret: A secret key shared between your application and the client application used to sign the requests and authenticate the client application.

HTTP header

The signature must be sent in the HTTP header "Authorization" with the authentication scheme "Signature" :

Authorization: Signature keyId="API_KEY",algorithm="hmac-sha256",headers="(request-target) host date digest content-length",signature="Base64(HMAC-SHA256(signing string))"  

Let's see the different components of the signature :

  • keyId (REQUIRED) : The client application's key;
  • algorithm (REQUIRED) : The algorithm used to create the signature;
  • header (OPTIONAL) : The list of HTTP headers used to create the signature of the request. If specified, it should be a lowercased, quoted list of HTTP header fields, separated by a single space character. If not specified, the Date header is used by default therefore the client must send this Date header. Note : The list order is important, and must be specified in the order the HTTP header field-value pairs are concatenated together during signing.
  • signature (REQUIRED) : A base 64 encoded digital signature. The client uses the algorithm and headers signature parameters to form a canonicalized signing string.

Signature string construction

To generate the string that is signed with the shared secret and the algorithm, the client must use the values of each HTTP header field in the headers Signature parameter in the order they appear.

To include the HTTP request target in the signature calculation, use the special (request-target) header field name.

  1. If the header field name is (request-target) then generate the header field value by concatenating the lowercased HTTP method, an ASCII space, and the path pseudo-headers (example : get /protected);
  2. Create the header field string by concatenating the lowercased header field name followed with an ASCII colon :, an ASCII space `` and the header field value. If there are multiple instances of the same header field, all header field values associated with the header field must be concatenated, separated by a ASCII comma and an ASCII space ,, and used in the order in which they will appear in the HTTP request;
  3. If value is not the last value then append an ASCII newline \n.

To illustrate the rules specified above, assume a headers parameter list with the value of (request-target) host date cache-control x-test with the following HTTP request headers:

GET /protected HTTP/1.1  
Host: example.org  
Date: Tue, 10 Apr 2018 10:30:32 GMT  
x-test: Hello world  
Cache-Control: max-age=60  
Cache-Control: must-revalidate  

For the HTTP request headers above, the corresponding signature string is:

(request-target): get /protected  
host: example.org  
date: Tue, 10 Apr 2018 10:30:32 GMT  
cache-control: max-age=60, must-revalidate  
x-test: Hello world  

Signature creation

In order to create a signature, a client must :

  1. Create the signature string as described in signature string construction;

  2. The algorithm and shared secret associated with keyId must then be used to generate a digital signature on the signature string;

  3. The signature is then generated by base 64 encoding the output of the digital signature algorithm.

Supported algorithms

Currently supported algorithm names are:

  • hmac-sha1
  • hmac-sha256
  • hmac-sha512

License

Licensed under MIT.