Coder Social home page Coder Social logo

fastify-api-key's Introduction

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

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.

fastify-api-key's People

Contributors

arkerone 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

Watchers

 avatar  avatar

fastify-api-key's Issues

Is there a way for server to enforce the headers?

Thank you for this plugin. I've been looking over the code and wondering if there's a way to specify a minimum list of headers that the server can require in the plugin. If I'm reading the code correctly, it appears the client can choose which headers are used to calculate the HMAC and the server won't enforce any minimum set of headers while parsing the Auth string. Did I misunderstand that?

My concern is this could lead to a downgrade attack, where an incorrectly configured client could just specify fewer fields, and the server would happily honor the request. I assume specifying more fields means you potentially have more entropy and the hmac is more resistant to attacks?

Also if I understand the spec correctly, one would need to issue an OPTIONS request, much like CORS, to get a list of the headers needed for the signature?

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.