{"id":32932921,"url":"https://github.com/rameerez/api_keys","last_synced_at":"2025-11-11T18:39:32.077Z","repository":{"id":290803899,"uuid":"971760048","full_name":"rameerez/api_keys","owner":"rameerez","description":"🔑 Secure API keys for your Rails app","archived":false,"fork":false,"pushed_at":"2025-08-08T00:36:58.000Z","size":315,"stargazers_count":35,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-07T12:11:49.975Z","etag":null,"topics":["api","api-client","api-rest","api-server","apis","auth","authentication","authorization","bearer-authentication","bearer-tokens","curl","rails","rails-api","ruby","ruby-gem","ruby-on-rails","rubygems","token","token-authetication","token-based-authentication"],"latest_commit_sha":null,"homepage":"https://apikeys.rameerez.com","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/rameerez.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,"zenodo":null}},"created_at":"2025-04-24T02:46:27.000Z","updated_at":"2025-08-10T16:18:39.000Z","dependencies_parsed_at":null,"dependency_job_id":"37d61354-e13a-406f-b4c3-d7e2d6b69f82","html_url":"https://github.com/rameerez/api_keys","commit_stats":null,"previous_names":["rameerez/api_keys"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/rameerez/api_keys","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rameerez%2Fapi_keys","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rameerez%2Fapi_keys/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rameerez%2Fapi_keys/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rameerez%2Fapi_keys/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rameerez","download_url":"https://codeload.github.com/rameerez/api_keys/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rameerez%2Fapi_keys/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":283910127,"owners_count":26915128,"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-11-11T02:00:06.610Z","response_time":65,"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":["api","api-client","api-rest","api-server","apis","auth","authentication","authorization","bearer-authentication","bearer-tokens","curl","rails","rails-api","ruby","ruby-gem","ruby-on-rails","rubygems","token","token-authetication","token-based-authentication"],"created_at":"2025-11-11T18:39:30.760Z","updated_at":"2025-11-11T18:39:32.068Z","avatar_url":"https://github.com/rameerez.png","language":"Ruby","readme":"# 🔑 `api_keys` – Secure API keys for your Rails app\n\n[![Gem Version](https://badge.fury.io/rb/api_keys.svg)](https://badge.fury.io/rb/api_keys)\n\n`api_keys` makes it simple to add secure, production-ready API key authentication to any Rails app. Generate keys, restrict scopes, auto-expire tokens, revoke tokens, gate endpoints. It also provides a self-serve dashboard for your users to self-issue and manage their API keys themselves. All tokens are hashed securely by default, and never stored in plaintext.\n\n[ 🟢 [Live interactive demo website](https://apikeys.rameerez.com) ]\n\n![API Keys Dashboard](api_keys_dashboard.webp)\n\nCheck out my other 💎 Ruby gems: [`allgood`](https://github.com/rameerez/allgood) · [`usage_credits`](https://github.com/rameerez/usage_credits) · [`profitable`](https://github.com/rameerez/profitable) · [`nondisposable`](https://github.com/rameerez/nondisposable)\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem \"api_keys\"\n```\n\nAnd then `bundle install`. After the gem is installed, run the generator and migration:\n\n```bash\nrails g api_keys:install\nrails db:migrate\n```\n\nAnd you're done!\n\n## Quick Start\n\nJust add `has_api_keys` to your desired model. For example, if you want your `User` records to have API keys, you'd have:\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_api_keys\nend\n```\n\nYou can also customize how many maximum keys your users can have by passing a block to `has_api_keys`, like this:\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_api_keys do\n    max_keys 10 # only 10 active API keys per user allowed\n    require_name true # always require users to set a name for each API key\n  end\nend\n```\n\nIt'd work the same if you want your `Organization` or your `Project` records to have API keys.\n\n### Mount the dashboard engine so your users can self-serve API keys\n\nThe goal of `api_keys` is to allow you to turn your Rails app into an API platform with secure key authentication in minutes, as in: drop in this gem and you're pretty much done with API key management.\n\nTo achieve that, the gem provides a ready-to-go dashboard you can just mount in your `routes.rb` like this:\n\n```ruby\nmount ApiKeys::Engine =\u003e '/settings/api-keys'\n```\n\n#### Default behavior (User-owned keys)\n\nBy default, the dashboard expects:\n- A `current_user` method that returns the currently logged-in user\n- An `authenticate_user!` method that ensures a user is logged in\n\nThis works out-of-the-box with Devise and most authentication solutions where `User` owns the API keys.\n\n#### Custom owner models (Organization, Team, etc.)\n\nIf your API keys belong to a different model (e.g., `Organization`), you **must** configure the dashboard in your initializer:\n\n```ruby\n# config/initializers/api_keys.rb\nApiKeys.configure do |config|\n  # Tell the dashboard how to find the current API key owner\n  config.current_owner_method = :current_organization\n  \n  # Tell the dashboard how to ensure the owner is authenticated\n  config.authenticate_owner_method = :authenticate_organization!\nend\n```\n\nThese methods must exist in your `ApplicationController` (or wherever the engine is mounted). For example:\n\n```ruby\nclass ApplicationController \u003c ActionController::Base\n  def current_organization\n    # Your logic to return the current organization\n    @current_organization ||= Organization.find(session[:organization_id])\n  end\n  \n  def authenticate_organization!\n    redirect_to login_path unless current_organization\n  end\nend\n```\n\n#### Common scenarios\n\n**Organization with user membership:**\n```ruby\n# When organizations own keys but users manage them\nconfig.current_owner_method = :current_organization\nconfig.authenticate_owner_method = :require_organization_member!\n```\n\n**Multi-tenant applications:**\n```ruby\n# When each tenant/account owns keys\nconfig.current_owner_method = :current_account\nconfig.authenticate_owner_method = :authenticate_account!\n```\n\n**Team-based ownership:**\n```ruby\n# When teams own keys\nconfig.current_owner_method = :current_team  \nconfig.authenticate_owner_method = :ensure_team_access!\n```\n\n#### What the dashboard provides\n\nOnce configured, your users can:\n- self-issue new API keys\n- set expiration dates\n- attach scopes / permissions to individual keys\n- add and edit the key names\n- revoke instantly\n- see the status of all their keys\n\nIt provides an UI with everything you'd expect from an API keys dashboard, working right out of the box:\n\n![API Keys Dashboard](api_keys_token.webp)\n\nTo make the experience between your app and the `api_keys` dashboard more seamless, you can configure a `return_url` and `return_text` so your users can quickly go back to your app or settings page (in the screenshot above, that's the \"Home\" links, text customizable)\n\nYou can check out the dashboard on the [live demo website](https://apikeys.rameerez.com).\n\n\n## How it works\n\n### Issuing new API keys\n\nIf you want to write your own front-end instead of using the provided dashboard, or just want to issue API keys at any point, you can do it with `create_api_key!`:\n\n```ruby\n@api_key = @user.create_api_key!(\n  name: \"my-key\",\n  scopes: \"['read', 'write']\",\n  expires_at: 42.days.from_now\n)\n\n# Get the plaintext token only available upon creation\nplaintext_token = @api_key.token\n# =\u003e ak_123abc...\n```\n\nFor security reasons, the **gem does not store the generated key** in the database.\n\nWe only store a salted hash, so the API key / API token itself is only available in plaintext immediately after creation, as `@api_key.token` – the `.token` method won't work any other time.\n\nWith this token, your users can make calls to your endpoints by attaching it as an `\"Authorization: Bearer ak_123abc...\"` in their HTTP calls headers, like this:\n\n```bash\ncurl -X GET -H \"Authorization: Bearer ak_123abc...\"   \"http://example.com/api/endpoint\"\n```\n\n### Listing all keys for users\n\nOf course, you can list all API keys for any record like this:\n```ruby\n  @user.api_keys\n```\n\nYou can filter by active keys, expired keys, revoked keys:\n```ruby\n  @user.api_keys.active\n  @user.api_keys.expired\n  @user.api_keys.revoked\n  @user.api_keys.inactive # expired or revoked\n```\n\n### Useful API key methods\n\nCheck if an API key is still active and therefore allowed to perform actions:\n```ruby\n@api_key.active?\n# =\u003e true\n```\n\nOr expired:\n```ruby\n@api_key.expired?\n# =\u003e false\n```\n\nYou can revoke (disable, make inactive) any API key at any point like this:\n```ruby\n@api_key.revoke!\n```\n\nAnd you can check if an API key is revoked like this:\n```ruby\n@api_key.revoked?\n# =\u003e true\n```\n\nAnd for any API key, you can always display a safe, user-friendly masked token to display on user interfaces so users can easily identify their keys:\n```ruby\n@api_key.masked_token\n# =\u003e \"ak_demo_••••yZn9\"\n```\n\n### Scopes: define and verify API Key permissions\n\nUsers can limit what each API key does by selecting scopes, and you can define those scopes.\n\nIn the `config/initializers/api_keys.rb` initializer generated when you installed the gem, you'll find an option to define global scopes:\n```ruby\nconfig.default_scopes = [\"read\", \"write\"]\n```\n\nThese will be the available permissions you'll see, for example, in the API Keys dashboard:\n\n![API Keys Dashboard](api_keys_permissions.webp)\n\nYou can also define per-model scopes by passing the option to the `has_api_keys` block, which overrides global defaults:\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_api_keys do\n    max_keys 10\n    default_scopes %w[read write admin]\n  end\nend\n```\n\nYou can get as granular with your scopes as you'd like, think for example AWS-like strings of the form: `\"s3:GetObject\"` – how you set this up is up to you! Scopes take any string: we recommend sticking to simple verbs (`\"read\"`, `\"write\"`) or `\"resource:action\"` (case-sensitive!)\n\nYou can check if an API key is allowed to do actions by checking its scopes:\n```ruby\n@api_key.allows_scope?(\"read\")\n# =\u003e true\n```\n\n## Controllers: secure your API endpoints\n\nTo add the `api_keys` functionality to your controllers, just use the `ApiKeys::Controller` concern and you'll have all controller methods available:\n```ruby\nclass ApiController \u003c ApplicationController\n  include ApiKeys::Controller # provides authenticate_api_key! and current_api_key_owner\nend\n```\n\nWith this, you get the `authenticate_api_key!` and `current_api_key_owner` methods, which come in handy to build your key-gated actions.\n\n### Require an API key for an endpoint\n\nIf you just want to check the presence of a valid (active, non-expired, non-revoked) API key for an endpoint, you can do:\n\n```ruby\nbefore_action :authenticate_api_key!\n```\n\nAnd of course, if you want to have unauthenticated endpoints:\n\n```ruby\nbefore_action :authenticate_api_key!, except: [:unauthenticated_endpoint]\n```\n\n`authenticate_api_key!` will return `401 Unauthenticated` for anything that's not a valid API key.\n\nIt will also load the valid API key, if any, to a `current_api_key` variable, that returns an API Key object (`ApiKeys::ApiKey`) on which you can call all the methods we've outlined above, and access any attribute, like:\n\n```ruby\ncurrent_api_key.expires_at\n# =\u003e 2025-05-25 05:25:05.250525000 UTC +00:00\n```\n\nIf the API key has an owner, you can also access it either with `current_api_key.owner` or with the helper method `current_api_key_owner`\n\nFor example, if the owner of the API key is a `User`, you might do something like:\n```ruby\ncurrent_api_key_owner.email\n# =\u003e john.doe@example.com\n```\n\n### Require a scope for an endpoint\n\nYou can require a specific scope for any endpoint like:\n```ruby\nauthenticate_api_key!(scope: \"write\")\n```\n\nIt may be cleaner if you pass it as a Proc to `before_action` – and it may result in better-organized code if you do it endpoint-per-endpoint, immediately before each method definition, like this:\n\n```ruby\nbefore_action -\u003e { authenticate_api_key!(scope: \"write\") }, only: [:write_action]\ndef write_action\n  # We'll only get here if the API key is active AND it has the right scope, so execute the actual logic of the endpoint and return success:\n  render json: {\n    # Your success JSON...\n  }, status: :ok\nend\n```\n\n### Rate limit your API endpoints\n\nRails 8 introduced the native, built-in `rate_limit` to easily rate limit your endpoints, so `Rack::Attack` is no longer necessary! While this is not an `api_keys` feature per se, I thought it'd be nice to include an example here because it pairs so well with `api_keys`.\n\nFor example, if you want to rate limit an endpoint to only accept 2 requests each 10 seconds, per API key, you'd do something like:\n```ruby\nbefore_action -\u003e { authenticate_api_key! }, only: [:rate_limited_action]\nrate_limit to: 2, within: 10.seconds,\n            by: -\u003e { current_api_key\u0026.id }, # Limit per API key ID\n            with: -\u003e { render json: { error: \"rate_limited\", message: \"Too many requests (max 2 per 10 seconds per key). Please wait.\" }, status: :too_many_requests },\n            only: [:rate_limited_action]\ndef rate_limited_action\n  render json: {\n    # Success JSON\n  }, status: :ok\nend\n```\n\nThis `rate_limit` feature depends on Rails 8+ and an active, well-configured cache store, like [`solid_cache`](https://github.com/rails/solid_cache), which comes by default in Rails 8.\n\nIf you're still on early versions of Rails, you can still use `api_keys`! No need to implement `rate_limit` – just an idea if you're already on Rails 8!\n\n\n## Configuration and settings\n\nThe gem installation creates an initializer at `config/initializers/api_keys.rb`\n\nThe default initializer is self-explanatory and self-documented, please consider spending a bit of time reading through it if you want to fine-tune the gem.\n\nSome highlights:\n\n### Accept API keys via query params instead of Authentication HTTP headers\n\nBy default, the `api_key` gem expects API keys to come *exclusively* as HTTP Authentication Bearer tokens, for security purposes. But you can allow users to make requests to your endpoints with the API key token passed as a URL query param too, like this:\n\n```\nhttps://example.com/api/endpoint?api_key=ak_123abc...\n```\n\nThis is not recommended security-wise because you'll be leaking API tokens everywhere in your logs, but if you want to enable this, just set the query param name you're expecting the API key token to be in:\n\n```ruby\nconfig.query_param = \"api_key\"\n```\n\n### Changing the hashing function to `bcrypt` for maximum security\n\nBy default, the `api_keys` gem hashes tokens using `sha256`, for fast token lookup and low-latency API authentication. Tokens are salted via their prefix, and only stored as secure digests.\n\nIf you need slower, password-grade hashing (e.g., for extremely sensitive tokens), you can switch to bcrypt:\n\n```ruby\nconfig.hash_strategy = :bcrypt\n```\n\nNote: bcrypt is ~50–100x slower than SHA256. For most API use cases, sha256 is more than sufficient.\n\n`sha256` has O(1) lookup, `bcrypt` doesn't. This means that if you switch to `bcrypt`, you may observe ~100ms lags on every API call, for every token auth that's not cached.\n\nFor 99% of APIs, `sha256` is more than secure enough — and far better for performance.\n\n### Increase cache TTL\n\nWe cache token lookups to improve performance, especially for repeated requests. This keeps `bcrypt` and `sha256` strategies fast under load.\n\nBy default, we use a 5-second TTL, which offers a strong balance: most requests benefit from caching, while revoked keys stop working almost immediately.\n\nIf security is your top priority (e.g. rapid revocation after suspected key compromise), you can disable caching entirely:\n\n```ruby\nconfig.cache_ttl = 0.seconds # disables caching\n```\n\nIf performance matters more than real-time revocation, increase the TTL to reduce DB hits:\n\n```ruby\nconfig.cache_ttl = 2.minutes # boosts performance at cost of slower revocation\n```\n\n⚠️ Security note: Revoked keys may remain valid for up to cache_ttl. For strict real-time revocation, set cache_ttl = 0.\n\n\n## Callbacks: analytics, logging, usage monitoring \u0026 auditing\n\nThe gem offers two callbacks that get executed every single time an API key is checked and authenticated (through `authenticate_api_key!` in controllers, for example)\n\nYou can define logic for them:\n```ruby\nconfig.before_authentication = -\u003e(request) { Rails.logger.info \"Authenticating request: #{request.uuid}\" }\n\nconfig.after_authentication = -\u003e(result) { MyAnalytics.track_auth(result) }\n```\n\nThis is especially useful if you want to build custom monitoring, usage tracking or auditing systems on top of the `api_keys` gem.\n\nSince these callbacks get called every single time an endpoint request is made, we can't just execute the code synchronously, blocking the thread and making the endpoint lag. Instead, we enqueue an async job that process the callback code, however long it is. You can configure which queue these jobs get enqueued to.\n\nThe downside of this, of course, is that callbacks will only work if you have a valid, well-configured Active Job backend for your Rails app, like Sidekiq or [`solid_queue`](https://github.com/rails/solid_queue/), which comes by default in Rails 8. If Active Job is not well configured, well, your callbacks just won't get executed.\n\nThere's also a `track_requests_count` config option that you can turn on so the gem keeps track of how many requests has each API key made. When this is on, you may access the count like this:\n```ruby\n@api_key.requests_count\n# =\u003e 4567\n```\n\nBut again, this is turned off by default for performance purposes, and depends on having a working, well-configured Active Job backend.\n\n## Enterprise-ready by design\nThe `api_keys` gem ships with:\n\n - Flexible storage\n - Async hooks\n - ActiveJob support\n - Polymorphic ownership (User, Org, etc.)\n - Custom scopes\n - Production caching\n - Tracking of last time each key was used\n - Usage tracking\n - SHA256 fallback\n\nMaking it enterprise-ready, built with extensibility and compliance in mind.\n\n## Roadmap\n  - [ ] Automatic rotation policy helpers\n  - [ ] Key-pair / HMAC option\n\n## Demo Rails app\n\nThere's a demo Rails app showcasing the features in the `api_keys` gem under `test/dummy`. It's currently deployed to `apikeys.rameerez.com`. If you want to run it yourself locally, you can just clone this repo, `cd` into the `test/dummy` folder, and then `bundle` and `rails s` to launch it. You can examine the code of the demo app to better understand the gem.\n\n## Testing\n\nRun the test suite with `bundle exec rake test`\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`.\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/rameerez/api_keys. Our code of conduct is: just be nice and make your mom proud of what you do and post online.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frameerez%2Fapi_keys","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frameerez%2Fapi_keys","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frameerez%2Fapi_keys/lists"}