HIGH api key exposurerailsopenid connect

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.

Frequently Asked Questions

Can OIDC metadata endpoints leak API keys?
They can if they expose internal configurations or are accessible without authentication. Restrict access and avoid including secrets in the response payload.
How should tokens be stored in a Rails OIDC integration to avoid exposure?
Use encrypted model attributes or Rails credentials, avoid logging tokens, and rotate keys regularly. Never store tokens or derived API keys in plaintext logs or cookies.