Api Key Exposure in Rails with Openid Connect
Api Key Exposure in Rails with Openid Connect — how this specific combination creates or exposes the vulnerability
In Rails applications that integrate OpenID Connect (OIDC), API keys may be exposed when secrets, tokens, or debug data leak into logs, error pages, or client-side artifacts. OIDC configurations that reference keys as environment variables can still expose those values if the app does not carefully control what is serialized, logged, or returned in HTTP responses.
One common pattern is storing the OIDC client secret or signing key as an environment variable (e.g., ENV["OIDC_CLIENT_SECRET"]) and using it during provider setup. If the initializer logs the configuration or a developer accidentally includes sensitive configuration in an error report, the key can be exposed. For example:
# config/initializers/oidc.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :openid_connect, ENV["OIDC_CLIENT_ID"], ENV["OIDC_CLIENT_SECRET"], issuer: ENV["OIDC_ISSUER"]
end
# Developer debugging in console or logs:
puts "OIDC config: #{Rails.application.config.middleware.detect { |h| h.inspect }}"
Such practices can lead to API keys appearing in development logs or terminal history. Additionally, if the app exposes introspection endpoints or metadata endpoints (e.g., /.well-known/openid-configuration) without access controls, internal service accounts or API keys used for backend calls may be inferred or extracted by unauthenticated attackers.
Another vector is improper handling of back-channel tokens. When Rails exchanges an authorization code for tokens, the application might store access or refresh tokens in logs or session stores with weak protections. If an attacker gains read access to logs or backups, they can recover API keys used to call downstream services. The risk increases when OIDC flows are combined with service-to-service calls where API keys are passed in headers without additional safeguards.
Middleware and instrumentation can also contribute to exposure. For instance, if request instrumentation captures full headers or parameters for debugging and those captures include authorization tokens or API keys, the data may be stored or viewed by broader teams than intended. Attackers who can view logs or error reports may leverage exposed API keys to pivot within the cloud environment or to third-party services.
To mitigate these risks, treat OIDC configuration as sensitive as any credential. Avoid logging configuration blocks, ensure token handling follows secure storage practices, and restrict metadata endpoints to authenticated or authorized consumers. Treat any inadvertently exposed API key as compromised and rotate it immediately.
Openid Connect-Specific Remediation in Rails — concrete code fixes
Remediation focuses on secure handling of secrets, strict access controls, and safe token lifecycle management. Below are concrete patterns and code examples that reduce the likelihood of API key exposure when using OpenID Connect in Rails.
1. Secure initializer with conditional logging
Keep OIDC configuration out of logs by avoiding string interpolation of sensitive values. Use environment variables and ensure debug modes do not dump configuration.
# config/initializers/oidc.rb
provider_opts = {
client_id: ENV["OIDC_CLIENT_ID"],
client_secret: ENV["OIDC_CLIENT_SECRET"],
issuer: ENV["OIDC_ISSUER"],
authorize_url: "/auth/realms/#{ENV["KEYCLOAK_REALM"]}/protocol/openid-connect/auth",
token_url: "/auth/realms/#{ENV["KEYCLOAK_REALM"]}/protocol/openid-connect/token",
userinfo_url: "/auth/realms/#{ENV["KEYCLOAK_REALM"]}/protocol/openid-connect/userinfo"
}
Rails.application.config.middleware.use OmniAuth::Builder do
provider :openid_connect, provider_opts[:client_id], provider_opts[:client_secret], {
issuer: provider_opts[:issuer],
authorize_params: { realm: ENV["KEYCLOAK_REALM"] },
token_params: { client_secret_method: :post }
}
end
# Ensure this initializer is not reloaded in development in a way that prints secrets:
if Rails.env.development? && ENV["LOG_OIDC_CONFIG"] != "true"
Rails.logger.info("OIDC initialized with environment variables; secret values are not logged.")
end
2. Secure token storage and usage
Do not store tokens or derived API keys in logs or cookies. Use encrypted attributes or Rails credentials for sensitive fields, and avoid passing keys in URLs or headers that may be captured.
# app/models/oidc_session.rb
class OidcSession < ApplicationRecord
# encrypted: access_token, refresh_token, api_key
encrypts :access_token, :refresh_token, :api_key
def self.from_hash(hash)
create!(
access_token: hash["access_token"],
refresh_token: hash["refresh_token"],
api_key: hash["api_key"]
)
end
end
3. Protect metadata and introspection endpoints
Ensure that /.well-known/openid-configuration and token introspection endpoints are not exposing secrets. Use authentication where appropriate and validate referer/origin headers to prevent leakage of internal configurations.
# config/routes.rb
Rails.application.routes.draw do
get "/.well-known/openid-configuration", to: oidc_well_known
post "/introspect", to: oidc_introspect
end
# app/controllers/oidc_controller.rb
class OidcController < ApplicationController
before_action :authenticate_service!, only: [:introspect]
def well_known
render json: {
issuer: ENV["OIDC_ISSUER"],
authorization_endpoint: "/auth/realms/#{ENV["KEYCLOAK_REALM"]}/protocol/openid-connect/auth",
token_endpoint: "/auth/realms/#{ENV["KEYCLOAK_REALM"]}/protocol/openid-connect/token",
userinfo_endpoint: "/auth/realms/#{ENV["KEYCLOAK_REALM"]}/protocol/openid-connect/userinfo"
}
end
def introspect
# Validate token and return minimal, non-sensitive metadata
render json: { active: true, scope: "api://default" }
end
private
def authenticate_service!
# Use mTLS or client credentials; do not expose API keys in parameters
head :unauthorized unless valid_service_token?(request.headers["Authorization"])
end
end
4. Avoid exposing keys via error handling
Ensure exceptions do not include API keys or tokens in error pages or JSON responses. Customize rescue handlers to return generic messages in production.
# config/application.rb
config.exceptions_app ->(env) { ErrorsController.action(:show).call(env) }
# app/controllers/errors_controller.rb
class ErrorsController < ApplicationController
def show
error = request.env["api.error"]
Rails.logger.error("API error: #{error.class} — message only")
render json: { error: "internal_server_error" }, status: :internal_server_error
end
end
By combining secure initializer practices, encrypted storage, endpoint protection, and careful error handling, you reduce the likelihood that API keys associated with OIDC flows are exposed unintentionally.