Class: Keycardai::OAuth::WebIdentity

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

Overview

private_key_jwt credential (RFC 7523) with auto-managed asymmetric keys: generates and persists an RSA-2048 keypair on first use, signs a short-lived client assertion on every token request, and exposes the public JWKS for the authorization server to verify. The recommended credential for production workloads that can persist a keypair; the private key never leaves the workload.

Holds no shared secret and contributes no Basic header. Never reads environment variables; the caller supplies storage configuration.

Instance Method Summary collapse

Constructor Details

#initialize(client_id:, server_name: nil, key_id: nil, storage: nil, storage_dir: nil, clock: -> { Time.now }) ⇒ WebIdentity

Returns a new instance of WebIdentity.

Parameters:

  • client_id (String)

    the registered OAuth client id, signed as the assertion's iss and sub

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

    sanitized into the key id when no key_id is given

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

    explicit key id (the JWT kid); otherwise derived from server_name, otherwise a generated UUID

  • storage (#load, #store, nil) (defaults to: nil)

    pluggable keypair storage

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

    directory for the default file store

  • clock (#call) (defaults to: -> { Time.now })

    returns the current Time; override in tests

Raises:



27
28
29
30
31
32
33
34
35
36
37
# File 'lib/keycardai/oauth/web_identity.rb', line 27

def initialize(client_id:, server_name: nil, key_id: nil, storage: nil, storage_dir: nil,
               clock: -> { Time.now })
  raise ConfigurationError, "WebIdentity requires a client_id" if client_id.nil? || client_id.empty?

  @client_id = client_id
  storage ||= FilePrivateKeyStorage.new(dir: storage_dir || FilePrivateKeyStorage::DEFAULT_DIR)
  @key_manager = PrivateKeyManager.new(
    key_id: key_id || sanitize(server_name) || SecureRandom.uuid,
    storage: storage, clock: clock
  )
end

Instance Method Details

#authorization_header(issuer: nil) ⇒ nil

Assertion-based authentication contributes no Basic header.

Parameters:

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

    unused; part of the Credential interface

Returns:

  • (nil)


48
49
50
# File 'lib/keycardai/oauth/web_identity.rb', line 48

def authorization_header(issuer: nil)
  nil
end

#client_jwks_url(base_url) ⇒ String

The conventional URL where this workload serves its public JWKS.

Parameters:

  • base_url (String)

    the workload's public base URL

Returns:

  • (String)


93
94
95
# File 'lib/keycardai/oauth/web_identity.rb', line 93

def client_jwks_url(base_url)
  "#{base_url.chomp("/")}/.well-known/jwks.json"
end

#key_idString

Returns the key id used as the assertion's kid.

Returns:

  • (String)

    the key id used as the assertion's kid



40
41
42
# File 'lib/keycardai/oauth/web_identity.rb', line 40

def key_id
  @key_manager.key_id
end

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

Build the token-exchange form parameters, signing a fresh client assertion for this request.

Parameters:

  • subject_token (String)
  • token_endpoint (String) (defaults to: nil)

    the assertion's audience

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

    unused; part of the Credential interface

Returns:

  • (Hash)

Raises:



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/keycardai/oauth/web_identity.rb', line 63

def prepare_token_exchange_request(subject_token:, token_endpoint: nil, resource: nil,
                                   audience: nil, scope: nil, issuer: nil)
  if token_endpoint.nil? || token_endpoint.empty?
    raise ConfigurationError, "WebIdentity requires the token_endpoint as the assertion audience"
  end

  {
    "grant_type" => GrantType::TOKEN_EXCHANGE,
    "subject_token" => subject_token,
    "subject_token_type" => TokenType::ACCESS_TOKEN,
    "resource" => resource,
    "audience" => audience,
    "scope" => scope,
    "client_assertion" => @key_manager.create_client_assertion(client_id: @client_id,
                                                               audience: token_endpoint),
    "client_assertion_type" => PrivateKeyManager::ASSERTION_TYPE
  }.compact
end

#public_jwksHash

The public JWKS the authorization server verifies assertions against.

Returns:

  • (Hash)

    => [...]



85
86
87
# File 'lib/keycardai/oauth/web_identity.rb', line 85

def public_jwks
  @key_manager.public_jwks
end