{"id":19177742,"url":"https://github.com/jessety/simple-hmac-auth","last_synced_at":"2025-05-07T20:41:23.084Z","repository":{"id":51115998,"uuid":"158595777","full_name":"jessety/simple-hmac-auth","owner":"jessety","description":"Protocol specification and Node library designed to make building APIs that use HMAC signatures simple","archived":false,"fork":false,"pushed_at":"2022-08-21T21:00:58.000Z","size":286,"stargazers_count":15,"open_issues_count":1,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-14T09:08:07.757Z","etag":null,"topics":["api-security","hmac-authentication","nodejs","request-signatures","request-signing","simple-hmac-auth"],"latest_commit_sha":null,"homepage":"https://npmjs.com/package/simple-hmac-auth","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jessety.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-11-21T19:19:26.000Z","updated_at":"2024-07-22T20:04:41.000Z","dependencies_parsed_at":"2022-09-19T13:10:31.309Z","dependency_job_id":null,"html_url":"https://github.com/jessety/simple-hmac-auth","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jessety%2Fsimple-hmac-auth","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jessety%2Fsimple-hmac-auth/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jessety%2Fsimple-hmac-auth/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jessety%2Fsimple-hmac-auth/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jessety","download_url":"https://codeload.github.com/jessety/simple-hmac-auth/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252954128,"owners_count":21830893,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-security","hmac-authentication","nodejs","request-signatures","request-signing","simple-hmac-auth"],"created_at":"2024-11-09T10:34:56.234Z","updated_at":"2025-05-07T20:41:23.060Z","avatar_url":"https://github.com/jessety.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# simple-hmac-auth\n\nHTTP authentication specification and Node library designed to make building APIs that use HMAC signatures simple.\n\n[![ci](https://github.com/jessety/simple-hmac-auth/workflows/ci/badge.svg)](https://github.com/jessety/simple-hmac-auth/actions)\n[![coverage](https://codecov.io/gh/jessety/simple-hmac-auth/branch/main/graph/badge.svg?token=DB9DUUWCT0)](https://codecov.io/gh/jessety/simple-hmac-auth)\n[![npm](https://img.shields.io/npm/v/simple-hmac-auth.svg)](https://www.npmjs.com/package/simple-hmac-auth)\n[![license](https://img.shields.io/github/license/jessety/simple-hmac-auth.svg)](https://github.com/jessety/simple-hmac-auth/blob/master/LICENSE)\n\n- [Specification](#specification)\n- [Server](#server)\n- [Client](#client)\n  - [Usage](#using-client)\n  - [Subclassing](#subclassing-client)\n- [Additional Implementations](#additional-implementations)\n  - [Koa Middleware](https://github.com/jessety/simple-hmac-auth-koa)\n  - [Express Middleware](https://github.com/jessety/simple-hmac-auth-express)\n  - [Swift Client](https://github.com/jessety/simple-hmac-auth-swift/)\n  - [Objective-C Client](https://github.com/jessety/simple-hmac-auth-objc/)\n  - [PHP Client](https://github.com/jessety/simple-hmac-auth-php/)\n\n## Specification\n\nFor all incoming requests, the HTTP method, path, query string, headers and body should be signed with a secret and sent as the request's \"signature.\" The headers should include the user's API key as well as a timestamp of when the request was made. On the server, the request signature is re-generated and confirmed against the signature from the client. If the signatures do not match the request is rejected. If the server receives a request with a timestamp older than five minutes it is also rejected.\n\nThis enables three things:\n\n- Verify the authenticity of the client\n- Prevent MITM attack\n- Protect against replay attacks\n\nThe client's authenticity is confirmed by their continued ability to produce signatures based on their secret. This approach also prevents man-in-the-middle attacks because any tampering would result in the signature mismatching the request's contents. Finally, replay attacks are prevented because signed requests with old timestamps will be rejected.\n\nRequest signatures are designed to be used in conjunction with HTTPS.\n\n### Headers\n\nEach request requires three headers: `authorization`, `signature` and either `date` or `timestamp`. If the HTTP request contains a body, the `content-length` and `content-type` headers are also required.\n\nThe `date` header is a standard [RFC-822 (updated in RFC-1123)](https://tools.ietf.org/html/rfc822#section-5) date, as per [RFC-7231](https://tools.ietf.org/html/rfc7231#section-7.1.1.2). Because it [cannot be programmatically set inside of a browser](https://fetch.spec.whatwg.org/#forbidden-header-name), the `timestamp` header may be substituted instead.\n\nThe `authorization` header is a standard as per [RFC-2617](https://tools.ietf.org/html/rfc2617#section-3.2.2) that, confusingly, is designed for authentication and not authorization. It should contain a string representation of the client's API key.\n\nThe `signature` header contains the signature of the entire request, as well as a reference to the version of the protocol, and the algorithm used to generate the signature.\n\u003e (Note: As per [RFC-6648](https://tools.ietf.org/html/rfc6648), X- prefixed headers should not be adopted for new protocols, and thus the prefix is omitted.)\n\nA correctly signed HTTP request may look like this:\n\n```text\n  POST https://localhost:443/api/items/\n  content-type: application/json\n  content-length: 90\n  date: Tue, 20 Apr 2016 18:48:24 GMT\n  authorization: api-key SAMPLE_API_KEY\n  signature: simple-hmac-auth sha256 64b0a4bd0cbb45c5b2fe8b1e4a15419b6018a9a90eb19046247af6a9e8896bd3\n```\n\n### Signature\n\nTo calculate the signature, the client first needs to create a string representation of the request. When the server receives an authenticated request it computes the the signature and compares it with the signature provided by the client. Therefore, the client must create a string representation of the request in the exact same way as the server. This is called \"canonicalization.\"\n\nThe format of a canonical representation of a request is:\n\n```text\n  HTTP Verb + \\n\n  URI + \\n\n  Canonical query string + \\n\n  Canonically formatted signed headers + \\n\n  Hashed body payload\n```\n\nThe canonical representations of these elements are as follows\n\n|Component|Format|Example|\n|---------|------|-------|\n|HTTP Verb | upperCase(verb) | POST, GET or DELETE |\n|URI | encode(uri) | /items/test%20item|\n|Query String | encode(paramA) + '=' + encode(valueA) + '\u0026' + encode(paramB) + '=' + encode(valueB) | paramA=valueA\u0026paramB=value%20B |\n|Headers | lowerCase(keyA) + ':' + trim(valueA) + '\\n' + lowerCase(keyB) + ':' + trim(valueB) | keyA:valueA\u003cbr\u003ekeyB:value%20B\n|Hashed payload | hex(hash('sha256', bodyData)) | ... |\n\nThe HTTP verb must be upper case. The URI should be url-encoded. The query string elements should be alphabetically sorted. The header keys must all be lower case (as per [RFC-2616](http://www.ietf.org/rfc/rfc2616.txt)) and alphabetically sorted. The only headers included in the signature should be: `authorization` and `date`- however `content-length` and `content-type` should be included if the HTTP body is not empty. The last line of the request string should be a hex representation of a SHA256 hash of the request body. If there is no request body, it should be the hash of an empty string.\n\nProgrammatically:\n\n```text\n  upperCase(method) + \\n\n  path + \\n\n  encode(paramA) + '=' + escape(valueA) + '\u0026' + escape(paramB) + '=' + escape(valueB) + \\n\n  lowerCase(headerKeyA) + ':' + trim(headerValueA) + \\n + lowerCase(headerKeyB) + ':' + trim(headerKeyB) + \\n\n  hex(hash('sha256', bodyData)) + \\n\n```\n\nFor Example\n\n```text\n  POST\n  /items/test\n  paramA=valueA\u0026paraB=value%20B\n  authorization: api-key SAMPLE_API_KEY\n  content-length:15\n  content-type: application/json\n  date:Tue, 20 Apr 2016 18:48:24 GMT\n  8eb2e35250a66c65d981393c74cead26a66c33c54c4d4a327c31d3e5f08b9e1b\n```\n\nThen the HMAC signature of the entire request is generated by signing it with the secret, as a hex representation:\n\n```text\nconst signature = hex(hmacSha256(secret, requestString))\n```\n\nThat value is then sent as the contents of the `signature` header along with the algorithm used to generate it, as well as the version of the protocol the signature implements.\n\n```javascript\nheaders[signature] = 'simple-hmac-auth sha256 ' + signature\n```\n\n## Usage\n\nReference implementation libraries for both servers and clients are included.\n\n### Server\n\nThe server implementation is a class that takes a request object and body data, signs the request, and compares the signature to the one sent by the client. If the signature is not valid, it will throw an error with an explanation as to why.\n\nMiddleware implementations for both [Express](https://github.com/jessety/simple-hmac-auth-express) and [Koa](https://github.com/jessety/simple-hmac-auth-koa) exist in their own repositories.\n\n#### Example\n\nFirst, instantiate the class.\n\n```javascript\nconst SimpleHMACAuth = require('simple-hmac-auth');\n\nconst auth = new SimpleHMACAuth.Server();\n```\n\nThe class requires a `secretForKey` function that returns the secret for a specified API key, if one exists. This function may return a value, execute a callback, or return a promise.\n\nAssuming a `secretForAPIKey` objects exists, the three following implementations are all valid.\n\n```javascript\n// Return\nauth.secretForKey = (apiKey) =\u003e {\n  return secretForAPIKey[apiKey];\n}\n\n// Callback\nauth.secretForKey = (apiKey, callback) =\u003e {\n  callback(null, secretForAPIKey[apiKey]);\n};\n\n// Promise\nauth.secretForKey = async (apiKey) =\u003e secretForAPIKey[apiKey];\n```\n\nFinally, create the server itself. Because the unparsed body must be hashed to authenticate a request, you must load the full body before calling `authenticate()`.\n\n```javascript\nhttp.createServer((request, response) =\u003e {\n\n  let data = '';\n\n  request.on('data', chunk =\u003e {\n    data += chunk.toString();\n  });\n\n  request.on('end', async () =\u003e {\n\n    try {\n\n      const { apiKey, signature } = await auth.authenticate(request, data);\n\n      console.log(`Authentication passed for request with API key \"${apiKey}\" and signature \"${signature}\".`);\n\n      response.writeHead(200);\n      response.end('200');\n\n    } catch (error) {\n\n      console.log(`  Authentication failed`, error);\n\n      response.writeHead(401);\n      response.end(error.message);\n    }\n  });\n}).listen(8000);\n```\n\nAlternatively, Sending a boolean `true` as the 2nd parameter instead of the raw body instructs `simple-hmac-auth` to handle the body itself.\n\n```javascript\nhttp.createServer((request, response) =\u003e {\n\n  try {\n\n      await auth.authenticate(request, true);\n\n      response.writeHead(200);\n      response.end('200');\n\n    } catch (error) {\n\n      response.writeHead(401);\n      response.end(error.message);\n    }\n}).listen(8000);\n```\n\n### Client\n\nThere are two ways to use the client class: directly, or by subclassing to make your own client. It supports using callbacks as well as promises, as well as serializing JavaScript objects as the query string or request.\n\n#### Using Client\n\nTo point it to your service, instantiate it with your host, port, and if you've enabled SSL yet.\n\n```javascript\nconst SimpleHMACAuth = require('simple-hmac-auth');\n\nconst client = new SimpleHMACAuth.Client('API_KEY', 'SECRET', {\n  host: 'localhost',\n  port: 8000,\n  ssl: false\n});\n```\n\nSet up the request options\n\n```javascript\nconst options = {\n  method: 'POST',\n  path: '/items/',\n  query: {\n    string: 'string',\n    boolean: true,\n    number: 42,\n    object: { populated: true },\n    array: [ 1, 2, 3 ]\n  },\n  data: {\n    string: 'string',\n    boolean: true,\n    number: 42,\n    object: { populated: true },\n    array: [ 1, 2, 3 ]\n  }\n};\n```\n\nTo make a request, execute the `.request()` function. It returns a promise, but will execute a callback if provided with one.\n\nCallback\n\n```javascript\nclient.request(options, (error, results) =\u003e {\n\n  if (error) {\n    console.error('Error:', error);\n    return;\n  }\n\n  console.log(results);\n});\n```\n\nPromise\n\n```javascript\nclient.request(options).then(results =\u003e {\n\n  console.log(results);\n\n}).catch(error =\u003e {\n\n  console.log('Error:', error);\n});\n```\n\nAsync promise\n\n```javascript\ntry {\n\n  const results = await client.request(options);\n\n  console.log(results);\n\n} catch (error) {\n\n  console.log('Error:', error);\n}\n```\n\n#### Subclassing Client\n\nTo write a client for your service, simply extend the class and add functions that match your API routes.\n\n```javascript\n\nconst SimpleHMACAuth = require('simple-hmac-auth');\n\nclass SampleClient extends SimpleHMACAuth.Client {\n\n  constructor(apiKey, secret, settings) {\n    super(apiKey, secret, settings);\n\n    self.settings.host = 'api.example.com';\n    self.settings.port = 443;\n    self.settings.ssl = true;\n  }\n\n  create(data, callback) {\n    return this.request({ method: 'POST', path: '/items/', data }, callback);\n  }\n\n  detail(id, parameters, callback) {\n    return this.request({ method: 'GET', path: '/items/' + encodeURIComponent(id), query: parameters }, callback);\n  }\n\n  query(parameters, callback) {\n    return this.request({ method: 'GET', path: '/items/', query: parameters }, callback);\n  }\n\n  update(id, data, callback) {\n    return this.request({ method: 'POST', path: '/items/' + encodeURIComponent(id), data }, callback);\n  }\n\n  delete(id, callback) {\n    return this.request({ method: 'DELETE', path: '/items/' + encodeURIComponent(id) }, callback);\n  }\n}\n\nmodule.exports = SampleClient;\n```\n\nBecause this client's constructor specified the host, port, and SSL status of the service, it can be instantiated with just `apiKey` and `secret`.\n\n```javascript\nconst client = new SampleClient(apiKey, secret);\n```\n\nJust like its parent class, this example subclass implements both promises and callbacks.\n\n```javascript\ntry {\n\n  const results = await client.query({ test: true });\n\n  console.log(results);\n\n} catch (error) {\n\n  console.log('Error:', error);\n}\n```\n\n```javascript\nclient.query({ test: true }, (error, results) =\u003e {\n\n  if (error) {\n    console.log('Error:', error);\n    return;\n  }\n\n  console.log(results);\n});\n\n```\n\n## Additional Implementations\n\nMiddleware for Express and Koa that leverage the implementation in this client exist in their own repositories. Compatible clients for iOS and PHP have also been implemented.\n\n- [Koa Middleware](https://github.com/jessety/simple-hmac-auth-koa)\n- [Express Middleware](https://github.com/jessety/simple-hmac-auth-express)\n- [Swift Client](https://github.com/jessety/simple-hmac-auth-swift/)\n- [Objective-C Client](https://github.com/jessety/simple-hmac-auth-objc/)\n- [PHP Client](https://github.com/jessety/simple-hmac-auth-php/)\n\n## License\n\nMIT © Jesse Youngblood\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjessety%2Fsimple-hmac-auth","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjessety%2Fsimple-hmac-auth","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjessety%2Fsimple-hmac-auth/lists"}