Api Key Exposure in Hanami with Openid Connect
Api Key Exposure in Hanami with OpenID Connect — how this specific combination creates or exposes the vulnerability
Hanami is a Ruby web framework that encourages modular, object-oriented design. When integrating OpenID Connect (OIDC), developers typically use gems such as omniauth-openid-connect or similar strategies to handle authentication. If configuration is incorrect, API keys or secrets intended for backend services can be exposed to the client-side or logged inadvertently during the OIDC flow.
One common pattern is storing third-party API keys in environment variables and referencing them within Hanami controllers or services. If a controller action renders these keys as part of a debug response, JSON payload, or error message, and the action is invoked while the user is authenticated via OIDC, the key can be exposed to the browser or an attacker who compromises the user’s session.
OIDC itself does not cause key exposure, but the combination increases risk when:
- Sensitive configuration (e.g.,
ENV["BACKEND_API_KEY"]) is passed into views or serialized into JSON responses during an authenticated request. - Error handling in Hanami controllers or middleware includes the full environment or request parameters, inadvertently including keys in logs or error pages accessible via OIDC-authenticated sessions.
- Misconfigured CORS or route helpers allow an OIDC-authenticated frontend to call endpoints that return sensitive configuration data.
For example, consider a Hanami controller that uses an API key to call a payment provider and returns a verbose response on failure:
module Web::Controllers::Payments
class Charge
include Web::Action
def call(params)
api_key = ENV.fetch("PAYMENT_API_KEY")
provider = PaymentProvider.new(api_key: api_key)
begin
result = provider.charge(params[:amount])
{ status: 200, response: result }
rescue StandardError => e
{ status: 500, error: e.message, api_key: api_key } # Risk: exposing key in error response
end
end
end
end
If this endpoint is reachable while the user has an authenticated OIDC session (e.g., the browser includes an ID token in an Authorization header), an attacker who can trigger or observe the error response may obtain the API key. This is particularly dangerous if the OIDC flow stores tokens in browser storage that are accessible to JavaScript, as the exposure may facilitate token theft or further lateral movement.
Another scenario involves logging. If Hanami applications log the full params or env during OIDC callback handling, keys present in environment variables could appear in log lines alongside user identity information, making correlation easier for an attacker with log access.
Because OIDC often introduces new endpoints (callback URLs) and modifies authentication state, it can change the attack surface. Ensure that keys are never serialized into responses, logs, or client-side artifacts when using OIDC with Hanami.
OpenID Connect-Specific Remediation in Hanami — concrete code fixes
Remediation focuses on isolating secrets from request/response cycles and tightening error handling. Apply these patterns consistently across Hanami apps that use OpenID Connect.
1. Avoid exposing secrets in responses and errors
Never include API keys in error payloads. Use a generic error message and log securely without sensitive data:
module Web::Controllers::Payments
class Charge
include Web::Action
def call(params)
api_key = ENV.fetch("PAYMENT_API_KEY")
provider = PaymentProvider.new(api_key: api_key)
begin
result = provider.charge(params[:amount])
{ status: 200, response: result }
rescue StandardError => e
# Log securely without exposing secrets
Hanami.logger.error("Payment charge failed: #{e.class} — #{e.message}")
{ status: 500, error: "Payment processing failed" }
end
end
end
end
2. Use secure storage and access patterns
Keep keys in environment variables and access them only in isolated service objects, not controllers:
# app/services/payment_provider.rb
class PaymentProvider
def initialize(api_key:)
@api_key = api_key
end
def charge(amount)
# call external provider using @api_key
end
end
# app/controllers/payments/charge.rb
module Web::Controllers::Payments
class Charge
include Web::Action
def call(params)
api_key = ENV.fetch("PAYMENT_API_KEY")
provider = PaymentProvider.new(api_key: api_key)
result = provider.charge(params[:amount])
{ status: 200, response: result }
end
end
end
3. Sanitize logs and audit OIDC callback handling
Configure Hanami’s logger to filter sensitive parameters. If you use a web framework that supports parameter filtering, ensure keys are masked. Also, avoid logging the full env during OIDC callback processing:
# config/initializers/hanami_logger.rb
Hanami.configure do |config|
config.logger = Logger.new($stdout)
# Ensure sensitive keys are not printed; filter at infrastructure level
end
4. Enforce HTTPS and validate OIDC configurations
Ensure that OIDC endpoints are served over HTTPS and that redirect URIs are explicitly registered. In Hanami, you can enforce SSL in production:
# config/environments/production.rb Rack::Builder.new do use Rack::SSL if ENV.fetch("RACK_ENV") == "production" run MyApp::WebRouter.new endAdditionally, validate that ID tokens are verified properly using the OIDC gem’s built-in checks to prevent token substitution attacks that could lead to privilege escalation or unauthorized access to sensitive endpoints.