{"id":13879833,"url":"https://github.com/mgomes/api_auth","last_synced_at":"2025-04-13T20:41:57.938Z","repository":{"id":648663,"uuid":"1474633","full_name":"mgomes/api_auth","owner":"mgomes","description":"HMAC authentication for Rails and HTTP Clients","archived":false,"fork":false,"pushed_at":"2024-03-16T12:00:40.000Z","size":448,"stargazers_count":479,"open_issues_count":18,"forks_count":149,"subscribers_count":15,"default_branch":"master","last_synced_at":"2025-04-06T17:07:32.803Z","etag":null,"topics":["api-authentication","hmac","hmac-authentication","hmac-md5","hmac-sha1","hmac-sha512","hmac-signature","ruby","ruby-on-rails"],"latest_commit_sha":null,"homepage":"","language":"Ruby","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/mgomes.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2011-03-13T14:45:26.000Z","updated_at":"2024-12-17T07:48:09.000Z","dependencies_parsed_at":"2024-06-18T13:41:55.176Z","dependency_job_id":null,"html_url":"https://github.com/mgomes/api_auth","commit_stats":{"total_commits":267,"total_committers":51,"mean_commits":5.235294117647059,"dds":0.6554307116104869,"last_synced_commit":"132205193cc54a9dfa17b3a8f5ddcf7aa1f4c727"},"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgomes%2Fapi_auth","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgomes%2Fapi_auth/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgomes%2Fapi_auth/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgomes%2Fapi_auth/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mgomes","download_url":"https://codeload.github.com/mgomes/api_auth/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248782265,"owners_count":21160716,"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-authentication","hmac","hmac-authentication","hmac-md5","hmac-sha1","hmac-sha512","hmac-signature","ruby","ruby-on-rails"],"created_at":"2024-08-06T08:02:35.021Z","updated_at":"2025-04-13T20:41:57.902Z","avatar_url":"https://github.com/mgomes.png","language":"Ruby","readme":"# ApiAuth\n\n[![Build Status](https://github.com/mgomes/api_auth/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/mgomes/api_auth/actions)\n[![Gem Version](https://badge.fury.io/rb/api-auth.svg)](https://badge.fury.io/rb/api-auth)\n\nLogins and passwords are for humans. Communication between applications need to\nbe protected through different means.\n\nApiAuth is a Ruby gem designed to be used both in your client and server\nHTTP-based applications. It implements the same authentication methods (HMAC-SHA2)\nused by Amazon Web Services.\n\nThe gem will sign your requests on the client side and authenticate that\nsignature on the server side. If your server resources are implemented as a\nRails ActiveResource, it will integrate with that. It will even generate the\nsecret keys necessary for your clients to sign their requests.\n\nSince it operates entirely using HTTP headers, the server component does not\nhave to be written in the same language as the clients.\n\n## How it works\n\n1. A canonical string is first created using your HTTP headers containing the\n`content-type`, `X-Authorization-Content-SHA256`, request path and the date/time stamp.\nIf `content-type` or `X-Authorization-Content-SHA256` are not present, then a blank\nstring is used in their place. If the timestamp isn't present, a valid HTTP date is\nautomatically added to the request. The canonical string is computed as follows:\n\n```ruby\ncanonical_string = \"#{http method},#{content-type},#{X-Authorization-Content-SHA256},#{request URI},#{timestamp}\"\n```\n\ne.g.,\n\n```ruby\ncanonical_string = 'POST,application/json,,request_path,Tue, 30 May 2017 03:51:43 GMT'\n```\n\n2. This string is then used to create the signature which is a Base64 encoded\nSHA1 HMAC, using the client's private secret key.\n\n3. This signature is then added as the `Authorization` HTTP header in the form:\n\n```ruby\nAuthorization = APIAuth \"#{client access id}:#{signature from step 2}\"\n```\n\nA cURL request would look like:\n\n```sh\ncurl -X POST --header 'Content-Type: application/json' --header \"Date: Tue, 30 May 2017 03:51:43 GMT\" --header \"Authorization: ${AUTHORIZATION}\"  https://my-app.com/request_path`\n```\n\n5. On the server side, the SHA2 HMAC is computed in the same way using the\nrequest headers and the client's secret key, which is known to only\nthe client and the server but can be looked up on the server using the client's\naccess id that was attached in the header. The access id can be any integer or\nstring that uniquely identifies the client. The signed request expires after 15\nminutes in order to avoid replay attacks.\n\n## References\n\n* [Hash functions](https://en.wikipedia.org/wiki/Cryptographic_hash_function)\n* [SHA-2 Hash function](https://en.wikipedia.org/wiki/SHA-2)\n* [HMAC algorithm](https://en.wikipedia.org/wiki/HMAC)\n* [RFC 2104 (HMAC)](https://tools.ietf.org/html/rfc2104)\n\n## Requirement\n\nThis gem require Ruby \u003e= 2.6 and Rails \u003e= 6.0 if you use rails.\n\n## Install\n\nThe gem doesn't have any dependencies outside of having a working OpenSSL\nconfiguration for your Ruby VM. To install:\n\n```sh\n[sudo] gem install api-auth\n```\n\nPlease note the dash in the name versus the underscore.\n\n## Clients\n\nApiAuth supports many popular HTTP clients. Support for other clients can be\nadded as a request driver.\n\nHere is the current list of supported request objects:\n\n* Net::HTTP\n* ActionDispatch::Request\n* Curb (Curl::Easy)\n* RestClient\n* Faraday\n* HTTPI\n* HTTP\n\n### HTTP Client Objects\n\nHere's a sample implementation of signing a request created with RestClient.\n\nAssuming you have a client access id and secret as follows:\n\n```ruby\n@access_id = \"1044\"\n@secret_key = ApiAuth.generate_secret_key\n```\n\nA typical RestClient PUT request may look like:\n\n```ruby\nheaders = { 'X-Authorization-Content-SHA256' =\u003e \"dWiCWEMZWMxeKM8W8Yuh/TbI29Hw5xUSXZWXEJv63+Y=\",\n  'Content-Type' =\u003e \"text/plain\",\n  'Date' =\u003e \"Mon, 23 Jan 1984 03:29:56 GMT\"\n}\n\n@request = RestClient::Request.new(\n    url: \"/resource.xml?foo=bar\u0026bar=foo\",\n    headers: headers,\n    method: :put\n)\n```\n\nTo sign that request, simply call the `sign!` method as follows:\n\n```ruby\n@signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)\n```\n\nThe proper `Authorization` request header has now been added to that request\nobject and it's ready to be transmitted. It's recommended that you sign the\nrequest as one of the last steps in building the request to ensure the headers\ndon't change after the signing process which would cause the authentication\ncheck to fail on the server side.\n\nIf you are signing a request for a driver that doesn't support automatic http\nmethod detection (like Curb or httpi), you can pass the http method as an option\ninto the sign! method like so:\n\n```ruby\n@signed_request = ApiAuth.sign!(@request, @access_id, @secret_key, :override_http_method =\u003e \"PUT\")\n```\n\nIf you want to use another digest existing in `OpenSSL::Digest`,\nyou can pass the http method as an option into the sign! method like so:\n\n```ruby\n@signed_request = ApiAuth.sign!(@request, @access_id, @secret_key, :digest =\u003e 'sha256')\n```\n\nWith the `digest` option, the `Authorization` header will be change from:\n\n```sh\nAuthorization = APIAuth 'client access id':'signature'\n```\n\nto:\n\n```sh\nAuthorization = APIAuth-HMAC-DIGEST_NAME 'client access id':'signature'\n```\n\n### ActiveResource Clients\n\nApiAuth can transparently protect your ActiveResource communications with a\nsingle configuration line:\n\n```ruby\nclass MyResource \u003c ActiveResource::Base\n  with_api_auth(access_id, secret_key)\nend\n```\n\nThis will automatically sign all outgoing ActiveResource requests from your app.\n\n### Flexirest\n\nApiAuth also works with [Flexirest](https://github.com/andyjeffries/flexirest) (used to be ActiveRestClient, but that is now unsupported) in a very similar way.\nSimply add this configuration to your Flexirest initializer in your app and it will automatically sign all outgoing requests.\n\n```ruby\nFlexirest::Base.api_auth_credentials(@access_id, @secret_key)\n```\n\n### Faraday\n\nApiAuth provides a middleware for adding authentication to a Faraday connection:\n\n```ruby\nrequire 'faraday/api_auth'\nFaraday.new do |f|\n  f.request :api_auth, @access_id, @secret_key\nend\n```\n\nThe order of middlewares is important. You should make sure api_auth is last.\n\n## Server\n\nApiAuth provides some built in methods to help you generate API keys for your\nclients as well as verifying incoming API requests.\n\nTo generate a Base64 encoded API key for a client:\n\n```ruby\nApiAuth.generate_secret_key\n```\n\nTo validate whether or not a request is authentic:\n\n```ruby\nApiAuth.authentic?(signed_request, secret_key)\n```\n\nThe `authentic?` method uses the digest specified in the `Authorization` header.\nFor example SHA256 for:\n\n```sh\nAuthorization = APIAuth-HMAC-SHA256 'client access id':'signature'\n```\n\nAnd by default SHA1 if the HMAC-DIGEST is not specified.\n\nIf you want to force the usage of another digest method, you should pass it as an option parameter:\n\n```ruby\nApiAuth.authentic?(signed_request, secret_key, :digest =\u003e 'sha256')\n```\n\nFor security, requests dated older or newer than a certain timespan are considered inauthentic.\n\nThis prevents old requests from being reused in replay attacks, and also ensures requests\ncan't be dated into the far future.\n\nThe default span is 15 minutes, but you can override this:\n\n```ruby\nApiAuth.authentic?(signed_request, secret_key, :clock_skew =\u003e 60) # or 1.minute in ActiveSupport\n```\n\nIf you want to sign custom headers, you can pass them as an array of strings in the options like so:\n\n``` ruby\nApiAuth.authentic?(signed_request, secret_key, headers_to_sign: %w[HTTP_HEADER_NAME])\n```\n\nWith the specified headers values being at the end of the canonical string in the same order.\n\nIf your server is a Rails app, the signed request will be the `request` object.\n\nIn order to obtain the secret key for the client, you first need to look up the\nclient's access_id. ApiAuth can pull that from the request headers for you:\n\n``` ruby\nApiAuth.access_id(signed_request)\n```\n\nOnce you've looked up the client's record via the access id, you can then verify\nwhether or not the request is authentic. Typically, the access id for the client\nwill be their record's primary key in the DB that stores the record or some other\npublic unique identifier for the client.\n\nHere's a sample method that can be used in a `before_action` if your server is a\nRails app:\n\n``` ruby\nbefore_action :api_authenticate\n\ndef api_authenticate\n  @current_account = Account.find_by_access_id(ApiAuth.access_id(request))\n  head(:unauthorized) unless @current_account \u0026\u0026 ApiAuth.authentic?(request, @current_account.secret_key)\nend\n```\n\n## Development\n\nApiAuth uses bundler for gem dependencies and RSpec for testing. Developing the\ngem requires that you have all supported HTTP clients installed. Bundler will\ntake care of all that for you.\n\nTo run the tests:\n\nInstall the dependencies for a particular Rails version by specifying a gemfile in `gemfiles` directory:\n\n```sh\nBUNDLE_GEMFILE=gemfiles/rails_5.gemfile bundle install\n```\n\nRun the tests with those dependencies:\n\n```sh\nBUNDLE_GEMFILE=gemfiles/rails_5.gemfile bundle exec rake\n```\n\nIf you'd like to add support for additional HTTP clients, check out the already\nimplemented drivers in `lib/api_auth/request_drivers` for reference. All of\nthe public methods for each driver are required to be implemented by your driver.\n\n## Authors\n\n* [Mauricio Gomes](https://github.com/mgomes)\n* [Kevin Glowacz](https://github.com/kjg)\n* [Florian Wininger](https://github.com/fwininger)\n\n## Copyright\n\nCopyright (c) 2014 Mauricio Gomes. See LICENSE.txt for further details.\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmgomes%2Fapi_auth","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmgomes%2Fapi_auth","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmgomes%2Fapi_auth/lists"}