Class: Verikloak::Discovery

Inherits:
Object
  • Object
show all
Defined in:
lib/verikloak/discovery.rb

Overview

Fetches and caches the OpenID Connect Discovery document.

This class retrieves the discovery metadata from an OpenID Connect provider (e.g., Keycloak) using the ‘.well-known/openid-configuration` endpoint. It validates required fields such as `jwks_uri` and `issuer`, and supports:

  • Dependency Injection of Faraday connection for testing and middleware

  • In-memory caching with configurable TTL

  • Thread safety via Mutex

  • Automatic handling of common HTTP statuses (including multi-hop redirects)

### Thread-safety ‘#fetch!` is synchronized, so concurrent callers share the same cached value and refresh.

### Errors Raises DiscoveryError with one of the following ‘code`s:

  • ‘invalid_discovery_url`

  • ‘discovery_metadata_fetch_failed`

  • ‘discovery_metadata_invalid`

  • ‘discovery_redirect_error`

Examples:

Basic usage

discovery = Verikloak::Discovery.new(
  discovery_url: "https://keycloak.example.com/realms/demo/.well-known/openid-configuration"
)
json = discovery.fetch!
json["issuer"]   #=> "https://keycloak.example.com/realms/demo"
json["jwks_uri"] #=> "https://keycloak.example.com/realms/demo/protocol/openid-connect/certs"

Constant Summary collapse

REQUIRED_FIELDS =

Required keys that must be present in the discovery document.

Returns:

  • (Array<String>)
%w[jwks_uri issuer].freeze

Instance Method Summary collapse

Constructor Details

#initialize(discovery_url:, connection: Verikloak::HTTP.default_connection, cache_ttl: 3600) ⇒ Discovery

Returns a new instance of Discovery.

Parameters:

  • discovery_url (String)

    The full URL to the ‘.well-known/openid-configuration`.

  • connection (Faraday::Connection) (defaults to: Verikloak::HTTP.default_connection)

    Optional Faraday client (for DI/tests).

  • cache_ttl (Integer) (defaults to: 3600)

    Cache TTL in seconds (default: 3600).

Raises:

  • (DiscoveryError)

    when ‘discovery_url` is not a valid HTTP(S) URL



47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/verikloak/discovery.rb', line 47

def initialize(discovery_url:, connection: Verikloak::HTTP.default_connection, cache_ttl: 3600)
  unless discovery_url.is_a?(String) && discovery_url.strip.match?(%r{^https?://})
    raise DiscoveryError.new('Invalid discovery URL: must be a non-empty HTTP(S) URL',
                             code: 'invalid_discovery_url')
  end

  @discovery_url = discovery_url
  @conn          = connection
  @cache_ttl     = cache_ttl
  @cached_json   = nil
  @fetched_at    = nil
  @mutex         = Mutex.new
end

Instance Method Details

#fetch!Hash

Fetches and parses the discovery document, using the in-memory cache if fresh.

Cache freshness is determined by ‘cache_ttl` from initialization.

Returns:

  • (Hash)

    Parsed JSON object containing discovery metadata.

Raises:

  • (DiscoveryError)

    if the request fails, the response is invalid, or required fields are missing.



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/verikloak/discovery.rb', line 67

def fetch!
  @mutex.synchronize do
    # Return cached if within TTL
    return @cached_json if @cached_json && (Time.now - @fetched_at) < @cache_ttl

    # Fetch fresh document
    json = with_error_handling { fetch_and_parse_json_from_url }
    validate_required_fields!(json)

    # Update cache
    @cached_json = json
    @fetched_at  = Time.now
    json
  end
end