UNPKG

api-signature

Version:

Express/Restify middleware to authenticate HTTP requests based on api key and signature

186 lines (157 loc) 6.87 kB
# HTTP signature scheme The signature is based on this draft ["Signing HTTP Messages"](https://tools.ietf.org/html/draft-cavage-http-signatures-09). 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 [](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](#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 ###### Python 3 <pre> import requests import base64 from datetime import datetime, timezone import hashlib import hmac import base64 api_key="assssassa" api_secret="ssasassaasaas" def sign_headers() -> dict: headers = dict() if api_key and api_secret: date = datetime.now(timezone.utc).strftime( '%a, %d %b %Y %H:%M:%S %Z') headers["Authorization"] = sign(date) headers["date"] = date return headers def encrypt(data) -> hmac.HMAC: message = bytes(data, 'utf-8') secret = bytes(api_secret, 'utf-8') hash = hmac.new(secret, message, hashlib.sha256) # to lowercase hexits return hash def sign(date): signature = base64.b64encode(encrypt(f"date: {date}").digest()).decode() print(date, signature) return f'Signature keyId="{api_key}",algorithm="hmac-sha256",signature="{signature}"' def main(): response = requests.get( url="/protected", headers=sign_headers() ) print(response) </pre> ###### Node.js <pre> const express = require('express'); const request = require('request'); const apiSignature = require('api-signature'); const crypto = require("crypto"); const app = express(); // Create the collection of api keys const apiKeys = new Map(); apiKeys.set('123456789', { id: 1, name: 'app1', secret: 'secret1' }); apiKeys.set('987654321', { id: 2, name: 'app2', secret: 'secret2' }); class Signer{ constructor(apiKey, apiSecret){ this.apiKey = apiKey; this.apiSecret = apiSecret; } signHeaders(){ const headers = {}; if (this.apiKey && this.apiKey){ const date = new Date().toUTCString(); headers.Authorization = this.sign(date); headers.date = date; } return headers; } encrypt(data){ //crypto.createHmac("sha256",api_secret).update("320755").digest("hex") const hash = crypto.createHmac("sha256",this.apiSecret).update(data).digest(); //to lowercase hexits return hash } sign(date){ const signature = Buffer.from(this.encrypt(`date: ${date}`)).toString("base64"); console.log(date, signature); return `Signature keyId="${this.apiKey}",algorithm="hmac-sha256",signature="${signature}"`; } } // Your function to get the secret associated to the key id function getSecret(keyId, done) { if (!apiKeys.has(keyId)) { return done(new Error('Unknown api key')); } const clientApp = apiKeys.get(keyId); done(null, clientApp.secret, { id: clientApp.id, name: clientApp.name }); } app.get('/unprotected', async (req, res) => { const signer = new Signer('123456789',apiKeys.get('123456789').secret); const response = await request.post( "http://localhost:8080/protected",{ headers: signer.signHeaders(), } ); console.log("Success"); response.pipe(res); }); app.post('/protected',apiSignature({ getSecret }),async (req, res) => { console.log("i got here"); res.send(`Hello ${req.credentials.name}`); }); app.listen(8080); </pre>