Skip to content

api_keys 0.8.0: config.require_bearer_scheme

Choose a tag to compare

@rameerez rameerez released this 25 Oct 19:11
· 1 commit to main since this release
Immutable release. Only release title and notes can be modified.
2bfbf5e

config.require_bearer_scheme makes the authentication header accept one credential shape only: Bearer <token>. One opt-in setting, no migration, no generator. The default keeps accepting a bare token exactly as every earlier version did.

Why

Since 0.1.0 the header has accepted two shapes: Authorization: Bearer ak_… and, as a fallback, the bare Authorization: ak_…. That fallback is convenient for curl, but a public API should parse one credential shape only. Two shapes mean two things to document, to test and to support forever, and a header such as Basic … or Token … was handed to the key lookup as if it were a token. ReplyBrain's public API asked for the strict shape; any app with a public API wants the same.

What changed

Added

  • config.require_bearer_scheme (Boolean, default false).

    ApiKeys.configure do |config|
      config.require_bearer_scheme = true
    end

    When true, the header named by config.header is accepted only when it matches Bearer <token>: the scheme in any case (bearer, BEARER), followed by one or more spaces or tabs, then the token. Everything else is refused:

    Header value false (default) true
    Bearer ak_… (any case, spaces or tabs) token token
    ak_… (bare token) token 401 invalid_token
    Basic …, Token … looked up as a token, fails 401 invalid_token, never looked up
    Bearer alone looked up as a token, fails 401 invalid_token, never looked up
    no header, config.query_param set query parameter query parameter

    A refused header is answered exactly like any wrong key: the ordinary 401 invalid_token through render_unauthorized, with no hint that the shape was the problem. It is never looked up, so it costs no database query or bcrypt comparison. The header was present, so a refused header never falls back to config.query_param. The setting applies to whichever header config.header names, not only Authorization.

    Like the other boolean settings, the writer accepts only true or false and raises ArgumentError otherwise.

  • Initializer template: rails g api_keys:install writes the commented setting under config.header, recommended for public APIs.

  • README: a new section, "Require the Bearer scheme".

Changed

  • README: the query parameter section says the gem expects keys in the Authorization header, instead of "exclusively as HTTP Authentication Bearer tokens", which the bare-token fallback contradicted.

Security

  • With the setting on, a credential in another scheme can no longer be mistaken for an API key, and the header is never parsed as a raw token. The refusal reuses the internal MALFORMED_CREDENTIAL sentinel that 0.7.0 introduced for oversized and invalid UTF-8 headers, so it takes the same constant-shape failure path and adds no new answer an attacker could distinguish.
  • No refusal statistics are stamped for a refused header: no key was identified.

Compatibility

  • Default false: nothing changes for any app that does not set it.
  • Turning it on breaks clients that send the bare token. Check your logs, or your SDKs and documentation, before you turn it on. With config.debug_logging = true the gem logs "Refused a header without the Bearer scheme." for each refusal.

Upgrade notes

No migration and no generator. Update the gem:

# Gemfile
gem "api_keys", "~> 0.8"
bundle update api_keys

To require the scheme, add one line to config/initializers/api_keys.rb:

config.require_bearer_scheme = true

The 0.7.0 upgrade notes still apply to apps coming from 0.6.x or earlier.

Tests

  • test/services/authenticator_test.rb: a bare token authenticates by default; with the setting, a bare token is an invalid token and is never looked up; the Bearer scheme still authenticates in any case and spacing; a refused header does not fall back to the query parameter; the setting applies to a custom config.header.
  • test/controllers/authentication_test.rb: through a controller, a bare token gets the ordinary 401 invalid_token answer.
  • Locally: 774 runs, 3424 assertions, 0 failures, 0 errors, 0 skips.

Automatically generated notes

What's Changed

  • api_keys 0.8.0: config.require_bearer_scheme by @rameerez in #78

Full Changelog: v0.7.0...v0.8.0