Class: Keycardai::OAuth::ClientSecret

Inherits:
Object
  • Object
show all
Defined in:
lib/keycardai/oauth/client_secret.rb

Overview

Static client_id + client_secret credential, authenticating with client_secret_basic (RFC 6749 ยง2.3.1 / RFC 7617). The simplest credential type, suitable for development and workloads that can safely hold a long-lived secret.

Implements the Credential interface: authorization_header(issuer:) plus prepare_token_exchange_request. Never reads environment variables; the caller sources the secret from its own config or secret manager.

Single zone:

ClientSecret.new("client_abc", "secret")

Multi-zone, keyed by the zone's issuer URL:

ClientSecret.new(
"https://acme.keycard.cloud" => ["client_a", "secret_a"],
"https://beta.keycard.cloud" => ["client_b", "secret_b"],
)

A token operation for a zone not in the map fails closed: the credential raises rather than fall back to another zone's secret.

Instance Method Summary collapse

Constructor Details

#initialize(client_id, client_secret) ⇒ ClientSecret #initialize(issuer_map) ⇒ ClientSecret

Returns a new instance of ClientSecret.

Overloads:

  • #initialize(issuer_map) ⇒ ClientSecret

    Parameters:

    • issuer_map (Hash{String => Array(String, String)})

Raises:



30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/keycardai/oauth/client_secret.rb', line 30

def initialize(client_id_or_map, client_secret = nil)
  if client_id_or_map.is_a?(Hash)
    raise ConfigurationError, "multi-zone ClientSecret requires at least one zone" if client_id_or_map.empty?

    @pairs = client_id_or_map.to_h do |issuer, (id, secret)|
      validate_pair(id, secret)
      [issuer.chomp("/"), [id, secret]]
    end
  else
    validate_pair(client_id_or_map, client_secret)
    @pair = [client_id_or_map, client_secret]
  end
end

Instance Method Details

#authorization_header(issuer: nil) ⇒ String

The HTTP Basic Authorization header for a token operation.

Parameters:

  • issuer (String, nil) (defaults to: nil)

    the target zone; required for multi-zone

Returns:

  • (String)

Raises:

  • (ConfigurationError)

    multi-zone lookup for an unconfigured zone (fails closed, never another zone's secret)



60
61
62
63
# File 'lib/keycardai/oauth/client_secret.rb', line 60

def authorization_header(issuer: nil)
  client_id, client_secret = resolve_pair(issuer)
  HTTP.basic_authorization(client_id, client_secret)
end

#issuersArray<String>

Returns the issuer URLs this credential can serve.

Returns:

  • (Array<String>)

    the issuer URLs this credential can serve



50
51
52
# File 'lib/keycardai/oauth/client_secret.rb', line 50

def issuers
  multi_zone? ? @pairs.keys : []
end

#multi_zone?Boolean

Returns whether this credential holds per-zone pairs.

Returns:

  • (Boolean)

    whether this credential holds per-zone pairs



45
46
47
# File 'lib/keycardai/oauth/client_secret.rb', line 45

def multi_zone?
  !@pairs.nil?
end

#prepare_token_exchange_request(subject_token:, resource: nil, audience: nil, scope: nil, token_endpoint: nil, issuer: nil) ⇒ Hash

Build the token-exchange form parameters. Client authentication rides in the Basic header, never in the body.

Parameters:

  • subject_token (String)
  • resource (String, nil) (defaults to: nil)
  • audience (String, nil) (defaults to: nil)
  • scope (String, nil) (defaults to: nil)
  • token_endpoint (String, nil) (defaults to: nil)

    unused; part of the interface

  • issuer (String, nil) (defaults to: nil)

    unused here; auth resolution is per-header

Returns:

  • (Hash)


75
76
77
78
79
80
81
82
83
84
85
# File 'lib/keycardai/oauth/client_secret.rb', line 75

def prepare_token_exchange_request(subject_token:, resource: nil, audience: nil, scope: nil,
                                   token_endpoint: nil, issuer: nil)
  {
    "grant_type" => GrantType::TOKEN_EXCHANGE,
    "subject_token" => subject_token,
    "subject_token_type" => TokenType::ACCESS_TOKEN,
    "resource" => resource,
    "audience" => audience,
    "scope" => scope
  }.compact
end