{"id":18510764,"url":"https://github.com/envato/jwt_signed_request","last_synced_at":"2025-09-11T01:09:31.147Z","repository":{"id":11185696,"uuid":"68666401","full_name":"envato/jwt_signed_request","owner":"envato","description":"Request signing and verification made easy","archived":false,"fork":false,"pushed_at":"2025-06-25T00:53:13.000Z","size":195,"stargazers_count":24,"open_issues_count":0,"forks_count":5,"subscribers_count":70,"default_branch":"master","last_synced_at":"2025-08-28T03:15:22.597Z","etag":null,"topics":["jwt","jwt-authentication","jwt-middleware"],"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/envato.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","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":"2016-09-20T02:36:07.000Z","updated_at":"2025-06-25T00:35:54.000Z","dependencies_parsed_at":"2024-11-06T15:30:49.693Z","dependency_job_id":"8f031132-200c-425a-9077-c15c3907a61e","html_url":"https://github.com/envato/jwt_signed_request","commit_stats":{"total_commits":176,"total_committers":20,"mean_commits":8.8,"dds":0.6818181818181819,"last_synced_commit":"4c59a89e5e37dbd4332d5e2f9875290658e3004e"},"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"purl":"pkg:github/envato/jwt_signed_request","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/envato%2Fjwt_signed_request","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/envato%2Fjwt_signed_request/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/envato%2Fjwt_signed_request/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/envato%2Fjwt_signed_request/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/envato","download_url":"https://codeload.github.com/envato/jwt_signed_request/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/envato%2Fjwt_signed_request/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274557583,"owners_count":25307516,"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","status":"online","status_checked_at":"2025-09-10T02:00:12.551Z","response_time":83,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["jwt","jwt-authentication","jwt-middleware"],"created_at":"2024-11-06T15:24:56.634Z","updated_at":"2025-09-11T01:09:31.124Z","avatar_url":"https://github.com/envato.png","language":"Ruby","readme":"# JWT Signed Request\n\n[![License MIT](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://github.com/envato/jwt_signed_request/blob/master/LICENSE.txt)\n[![Gem Version](https://img.shields.io/gem/v/jwt_signed_request.svg?maxAge=2592000)](https://rubygems.org/gems/jwt_signed_request)\n[![Gem Downloads](https://img.shields.io/gem/dt/jwt_signed_request.svg?maxAge=2592000)](https://rubygems.org/gems/jwt_signed_request)\n[![Test Suite](https://github.com/envato/jwt_signed_request/workflows/tests/badge.svg?branch=master)](https://github.com/envato/jwt_signed_request/actions?query=branch%3Amaster+workflow%3Atests)\n\nRequest signing and verification for Internal APIs using JWT.\n\n## Getting Started\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'jwt_signed_request'\n```\n\nthen run:\n\n```sh\n$ bundle\n```\n\n## Stale PRs\n\nWe use the \"stale\" workflow to manage our PRs.\nIf you have a PR open for 60 days without any activity, it will automatically be labelled `stale-pr`.\nIf there is no activity for 7 days after this label is applied, the PR will be automatically closed.\n\nIf you have a PR that has a sensible reason for being open for a long period of time with no activity, you can apply the `do-not-auto-close` label to avoid it being automatically closed.\n\n## Generating EC Keys\n\nWe should be using a public key encryption algorithm such as **ES256**. To generate your public/private key pair using **ES256** run:\n\n```sh\n$ openssl ecparam -genkey -name prime256v1 -noout -out myprivatekey.pem\n$ openssl ec -in myprivatekey.pem -pubout -out mypubkey.pem\n```\n\nStore and encrypt these in your application secrets.\n\n## Configuration\n\nYou can add signing and verification keys to one or more key stores as your application needs them.\n\nFor example, given the following keys:\n\n```ruby\nprivate_key = \u003c\u003c-PEM.gsub(/^\\s+/, \"\")\n    -----BEGIN EC PRIVATE KEY-----\n    MHcCAQEEIBOQ3YIILYMV1glTKbF9oeZWzHe3SNQjAx4IbPIxNygQoAoGCCqGSM49\n    AwEHoUQDQgAEuOC3ufTTnW0hVmCPNERb4LxaDE/OexDdlmXEjHYaixzYIduluGXd\n    3cjg4H2gjqsY/NCpJ9nM8/AAINSrq+qPuA==\n    -----END EC PRIVATE KEY-----\n  PEM\n\npublic_key = \u003c\u003c-PEM.gsub(/^\\s+/, \"\")\n  -----BEGIN PUBLIC KEY-----\n  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuOC3ufTTnW0hVmCPNERb4LxaDE/O\n  exDdlmXEjHYaixzYIduluGXd3cjg4H2gjqsY/NCpJ9nM8/AAINSrq+qPuA==\n  -----END PUBLIC KEY-----\nPEM\n```\n\n### Single key store\n\nIf your application only needs a single key store, configure it like so:\n\n```ruby\nrequire 'openssl'\n\nJWTSignedRequest.configure_keys do |config|\n  config.add_signing_key(\n    key_id: 'client_a',\n    key: OpenSSL::PKey::EC.new(private_key),\n    algorithm: 'ES256',\n  )\n\n  config.add_verification_key(\n    key_id: 'client_a',\n    key: OpenSSL::PKey::EC.new(public_key),\n    algorithm: 'ES256',\n  )\nend\n```\n\n### Multiple key stores\n\nIf your application requires multiple key stores, configure them like so:\n\n```ruby\nkey_store_id = 'widget_admin'\n\nJWTSignedRequest.configure_keys(key_store_id) do |config|\n  config.add_signing_key(\n    key_id: 'client_a',\n    key: OpenSSL::PKey::EC.new(private_key),\n    algorithm: 'ES256',\n  )\n\n  config.add_verification_key(\n    key_id: 'client_a',\n    key: OpenSSL::PKey::EC.new(public_key),\n    algorithm: 'ES256',\n  )\nend\n```\n\n## Signing Requests\n\nIf you have added your signing keys to a key store, you will only need to\nspecify the `key_id` you are signing the requests with.\n\nIf you are using multiple key stores, you will also need to pass the\nappropriate `key_store_id`.\n\n### Using net/http\n\n```ruby\nrequire 'net/http'\nrequire 'uri'\nrequire 'openssl'\nrequire 'jwt_signed_request'\n\nuri = URI('http://example.com')\nreq = Net::HTTP::Get.new(uri)\njwt_token = JWTSignedRequest.sign(\n  method: req.method,\n  path: req.path,\n  headers: {\"Content-Type\" =\u003e \"application/json\"},\n  body: \"\",\n  key_id: 'my-key-id',                    # used for looking up key and kid header\n  lookup_key_id: 'my-alt-key-id',         # optionally override lookup key\n  key_store_id: 'widget_admin',           # optionally specify named key store ID\n  issuer: 'my-issuer'                     # optional\n  additional_headers_to_sign: ['X-AUTH']  # optional\n)\n\nreq['Authorization'] = \"Bearer #{jwt_token}\"\n\nres = Net::HTTP.start(uri.hostname, uri.port) {|http|\n  http.request(req)\n}\n```\n\n### Using Faraday\n\n```ruby\nrequire 'faraday'\nrequire 'openssl'\nrequire 'jwt_signed_request/middlewares/faraday'\n\nconn = Faraday.new(url: URI.parse('http://example.com')) do |faraday|\n  faraday.use(\n    JWTSignedRequest::Middlewares::Faraday,\n      key_id: 'my-key-id',\n      key_store_id: 'my-key-store-id',        # optional\n      issuer: 'my-issuer',                    # optional\n      additional_headers_to_sign: ['X-AUTH'], # optional\n      bearer_schema: true,                    # optional\n    )\n\n  faraday.adapter Faraday.default_adapter\nend\n\nconn.post do |req|\n  req.url 'http://example.com'\n  req.body = '{ \"name\": \"Unagi\" }'\nend\n```\n\n#### Additional options\n\n##### bearer_schema (boolean)\n\nDetermines whether to use the [Bearer schema](https://auth0.com/docs/jwt#how-do-json-web-tokens-work-) when assigning the JWT token to the `Authorization` request header\n\n| bearer_schema value | Authorization header value|\n|---------------------|---------------------------|\n| false (default) | `\u003cjwt_token\u003e` |\n| true | `Bearer \u003cjwt_token\u003e` |\n\n\n## Verifying Requests\n\nPlease make sure you have added your verification keys to the appropriate key\nstore. Doing so will allow the server to verify requests signed by different\nsigning keys.\n\n## Using Rails\n\n```ruby\nclass APIController \u003c ApplicationController\n  before_action :verify_request\n\n  ...\n\n  private\n\n  def verify_request\n    begin\n      JWTSignedRequest.verify(\n        request: request,\n        # Use optional `key_store_id` kwarg when working with multiple key stores, eg:\n        key_store_id: 'widget_admin',\n      )\n\n    rescue JWTSignedRequest::UnauthorizedRequestError =\u003e e\n      render :json =\u003e {}, :status =\u003e :unauthorized\n    end\n  end\n\nend\n```\n\n### Increasing Expiry leeway\n\nJWT tokens contain an expiry timestamp. If communication delays are large (or system clocks are sufficiently out of synch), you may need to increase the 'leeway' when verifying. For example:\n\n```ruby\n  JWTSignedRequest.verify(request: request, leeway: 55)\n```\n\n## Using Rack Middleware\n\n```ruby\nclass Server \u003c Sinatra::Base\n  use(\n    JWTSignedRequest::Middlewares::Rack,\n    exclude_paths: /public|health/,          # optional regex\n    leeway: 100,                             # optional\n    key_store_id: 'my-key-store-id',         # optional\n  )\n end\n```\n\n## Backwards Compability\n\nPlease note that the way we sign and verify requests has changed in version 2.x.x. For documentation on how to use older versions please look [here](https://github.com/envato/jwt_signed_request/blob/master/VERSION_1.md).\n\nWe are only supporting the old API for the next couple of releases of version 2.x.x so please upgrade ASAP.\n\n## Maintainers\n- [Envato](https://github.com/envato)\n\n## License\n\n`JWTSignedRequest` uses MIT license. See\n[`LICENSE.txt`](https://github.com/envato/jwt_signed_request/blob/master/LICENSE.txt) for\ndetails.\n\n## Code of conduct\n\nWe welcome contribution from everyone. Read more about it in\n[`CODE_OF_CONDUCT.md`](https://github.com/envato/jwt_signed_request/blob/master/CODE_OF_CONDUCT.md)\n\n## Contributors\n\nMany thanks to the following contributors to this gem:\n\n- Toan Nguyen - [@yoshdog](https://github.com/yoshdog)\n- Odin Dutton - [@twe4ked](https://github.com/twe4ked)\n- Sebastian von Conrad - [@vonconrad](https://github.com/vonconrad)\n- Zubin Henner- [@zubin](https://github.com/zubin)\n- Glenn Tweedie - [@nocache](https://github.com/nocache)\n- Giancarlo Salamanca - [@salamagd](https://github.com/salamagd)\n- Ben Axnick - [@bentheax](https://github.com/bentheax)\n- Glen Stampoultzis - [@gstamp](https://github.com/gstamp)\n- Lucas Parry - [@lparry](https://github.com/lparry)\n- Chris Mckenzie - [@chrisface](https://github.com/chrisface)\n\n## Contributing\n\nFor bug fixes, documentation changes, and small features:\n\n1. Fork it ( https://github.com/envato/jwt_signed_request/fork )\n2. Create your feature branch (git checkout -b my-new-feature)\n3. Commit your changes (git commit -am 'Add some feature')\n4. Push to the branch (git push origin my-new-feature)\n5. Create a new Pull Request\n\nFor larger new features: Do everything as above, but first also make contact with the project maintainers to be sure your change fits with the project direction and you won't be wasting effort going in the wrong direction\n\n### Compatibility\n\nCompatibility with multiple versions of the [JWT gem] is tested via the [appraisal gem].\n\nConfigured versions are defined in [Appraisals](./Appraisals), which at time of writing looked like this:\n\n```ruby\n# Latest JWT minor versions\n# Source: https://rubygems.org/gems/jwt/versions\n%w[\n  1.5.6\n  2.0.0\n  2.1.0\n  2.2.1\n].each do |jwt_version|\n```\n\nEnsure you set up your local environment by running:\n\n```sh\nbundle exec appraisal install\n```\n\nRun the test suite like this:\n\n```sh\n# Test all configured versions\nbundle exec appraisal rspec\n\n# Target a specific configured version\nbundle exec appraisal jwt-1.5.6 rspec\n```\n\n[JWT gem]: https://github.com/jwt/ruby-jwt\n[appraisal gem]: https://github.com/thoughtbot/appraisal\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fenvato%2Fjwt_signed_request","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fenvato%2Fjwt_signed_request","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fenvato%2Fjwt_signed_request/lists"}