Module: Verikloak::ErrorResponse

Defined in:
lib/verikloak/error_response.rb

Overview

Shared helpers for building RFC 6750 compliant JSON error responses.

Provides a consistent format for error responses across all Verikloak middleware:

  • JSON body with ‘{ error:, message: }` structure

  • ‘WWW-Authenticate` header with Bearer scheme for 401 responses

  • Header value sanitization to prevent injection attacks

Class Method Summary collapse

Class Method Details

.build(code:, message:, status:, realm: 'verikloak') ⇒ Array(Integer, Hash, Array<String>)

Build a JSON error response with optional RFC 6750 ‘WWW-Authenticate` header.

Parameters:

  • code (String)

    The error code (e.g. “unauthorized”, “insufficient_audience”)

  • message (String)

    Human-readable error message

  • status (Integer)

    HTTP status code

  • realm (String) (defaults to: 'verikloak')

    The realm value for WWW-Authenticate (default: “verikloak”)

Returns:

  • (Array(Integer, Hash, Array<String>))

    Rack response triple



22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/verikloak/error_response.rb', line 22

def build(code:, message:, status:, realm: 'verikloak')
  body = { error: code, message: message }.to_json
  headers = { 'Content-Type' => 'application/json' }

  if status == 401
    realm_val = sanitize_header_value(realm)
    code_val  = sanitize_header_value(code)
    msg_val   = sanitize_header_value(message)
    headers['WWW-Authenticate'] =
      %(Bearer realm="#{realm_val}", error="#{code_val}", error_description="#{msg_val}")
  end

  [status, headers, [body]]
end

.sanitize_header_value(val) ⇒ String

Sanitizes a value for safe inclusion in HTTP header quoted-string fields. Escapes backslashes and double-quotes, and strips CR/LF and other control characters.

Parameters:

  • val (String, nil)

Returns:

  • (String)


42
43
44
45
46
47
48
49
50
# File 'lib/verikloak/error_response.rb', line 42

def sanitize_header_value(val)
  s = val.to_s
  # Truncate at first CRLF sequence to prevent header injection
  s = s.split("\r\n", 2).first.to_s
  # Replace remaining lone CR or LF with spaces
  s = s.gsub(/[\r\n]/, ' ')
  s.gsub(/(["\\])/) { |m| "\\#{m}" }
   .gsub(/[[:cntrl:]]/, '')
end