Class: MCPClient::Auth::PKCE

Inherits:
Object
  • Object
show all
Defined in:
lib/mcp_client/auth.rb

Overview

PKCE (Proof Key for Code Exchange) helper

Constant Summary collapse

ENDED_ISSUER =

Issuer value marking an authorization request that can no longer complete as this resource's: the resource left the authorization server the request was made with (see OAuthProvider::PendingRequests). The record stays in the pending slot, so a late callback is refused for that reason rather than mistaken for a stranger's, on every storage backend alike.

'urn:mcp:ended-request'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(code_verifier: nil, code_challenge: nil, code_challenge_method: nil, issuer: nil, iss_parameter_supported: nil, client_id: nil, redirect_uri: nil, state: nil, resource: nil, scope: nil) ⇒ PKCE

Generate PKCE parameters

Parameters:

  • code_verifier (String, nil) (defaults to: nil) —

    Existing code verifier (for deserialization)

  • code_challenge (String, nil) (defaults to: nil) —

    Existing code challenge (for deserialization)

  • code_challenge_method (String) (defaults to: nil) —

    Challenge method (default: 'S256')

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

    the selected authorization server's issuer, recorded with this per-request record for RFC 9207 validation of the authorization response (MCP 2026-07-28)

  • iss_parameter_supported (Boolean, nil) (defaults to: nil) —

    whether that authorization server advertised authorization_response_iss_parameter_supported, recorded with the request so the response is judged by the server the request went to

  • client_id (String, nil) (defaults to: nil) —

    the client id the authorization request was made with, so the code is redeemed with the same credentials

  • redirect_uri (String, nil) (defaults to: nil) —

    the redirect URI the authorization request was made with, so the code is redeemed with the same value (RFC 6749 Section 4.1.3)

  • state (String, nil) (defaults to: nil) —

    the state of the authorization request. MCP 2026-07-28 requires the issuer to be associated with "the same per-request record used to store the PKCE code verifier (and the state value, if used)": keeping the state in a slot of its own lets two flows sharing one storage backend interleave their writes until one flow's state names another flow's record, so it is recorded here as well and checked against the callback's state

  • resource (String, nil) (defaults to: nil) —

    the resource (the MCP server URL) the authorization request named, so the token the code buys is only ever kept for that resource — a provider retargeted at another resource of the same authorization server while the exchange is in flight must not store it as the other resource's token (MCP 2026-07-28 token audience binding)

  • scope (String, nil) (defaults to: nil) —

    the scope the authorization request asked for: a token response that omits scope granted exactly that (RFC 6749 Section 5.1), and the step-up union of a rebuilt provider must not lose it



590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
# File 'lib/mcp_client/auth.rb', line 590

def initialize(code_verifier: nil, code_challenge: nil, code_challenge_method: nil, issuer: nil,
               iss_parameter_supported: nil, client_id: nil, redirect_uri: nil, state: nil,
               resource: nil, scope: nil)
  @code_verifier = code_verifier || generate_code_verifier
  @code_challenge = code_challenge || generate_code_challenge(@code_verifier)
  @code_challenge_method = code_challenge_method || 'S256'
  @issuer = issuer
  # A record read back from a hash-persisting backend can carry
  # anything here. Not a boolean is not an answer: the record says
  # nothing about the `iss` parameter, so the authorization server's own
  # metadata decides (and that reading fails closed), rather than a
  # mangled value silently meaning "not advertised".
  @iss_parameter_supported = iss_parameter_supported if [true, false].include?(iss_parameter_supported)
  @client_id = client_id
  @redirect_uri = redirect_uri
  @state = state
  @resource = resource
  @scope = scope
end

Instance Attribute Details

#client_id ⇒ Object (readonly)

Returns the value of attribute client_id.



554
555
556
# File 'lib/mcp_client/auth.rb', line 554

def client_id
  @client_id
end

#code_challenge ⇒ Object (readonly)

Returns the value of attribute code_challenge.



554
555
556
# File 'lib/mcp_client/auth.rb', line 554

def code_challenge
  @code_challenge
end

#code_challenge_method ⇒ Object (readonly)

Returns the value of attribute code_challenge_method.



554
555
556
# File 'lib/mcp_client/auth.rb', line 554

def code_challenge_method
  @code_challenge_method
end

#code_verifier ⇒ Object (readonly)

Returns the value of attribute code_verifier.



554
555
556
# File 'lib/mcp_client/auth.rb', line 554

def code_verifier
  @code_verifier
end

#iss_parameter_supported ⇒ Object (readonly)

Returns the value of attribute iss_parameter_supported.



554
555
556
# File 'lib/mcp_client/auth.rb', line 554

def iss_parameter_supported
  @iss_parameter_supported
end

#issuer ⇒ Object (readonly)

Returns the value of attribute issuer.



554
555
556
# File 'lib/mcp_client/auth.rb', line 554

def issuer
  @issuer
end

#redirect_uri ⇒ Object (readonly)

Returns the value of attribute redirect_uri.



554
555
556
# File 'lib/mcp_client/auth.rb', line 554

def redirect_uri
  @redirect_uri
end

#resource ⇒ Object (readonly)

Returns the value of attribute resource.



554
555
556
# File 'lib/mcp_client/auth.rb', line 554

def resource
  @resource
end

#scope ⇒ Object (readonly)

Returns the value of attribute scope.



554
555
556
# File 'lib/mcp_client/auth.rb', line 554

def scope
  @scope
end

#state ⇒ Object (readonly)

Returns the value of attribute state.



554
555
556
# File 'lib/mcp_client/auth.rb', line 554

def state
  @state
end

Class Method Details

.from_h(data) ⇒ PKCE

Note:

code_challenge_method is optional and defaults to 'S256'. The code_challenge is not re-validated against code_verifier; callers are expected to provide values from a prior to_h round-trip.

Create PKCE instance from hash

Parameters:

  • data (Hash) —

    Hash with PKCE parameters (symbol or string keys)

Returns:

  • (PKCE) —

    New PKCE instance

Raises:

  • (ArgumentError) —

    If required parameters are missing



635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
# File 'lib/mcp_client/auth.rb', line 635

def self.from_h(data)
  verifier = data[:code_verifier] || data['code_verifier']
  challenge = data[:code_challenge] || data['code_challenge']
  method = data[:code_challenge_method] || data['code_challenge_method']
  issuer = data[:issuer] || data['issuer']
  supported = if data.key?(:iss_parameter_supported)
                data[:iss_parameter_supported]
              else
                data['iss_parameter_supported']
              end

  raise ArgumentError, 'Missing code_verifier' unless verifier
  raise ArgumentError, 'Missing code_challenge' unless challenge

  new(code_verifier: verifier, code_challenge: challenge, code_challenge_method: method, issuer: issuer,
      iss_parameter_supported: supported, client_id: data[:client_id] || data['client_id'],
      redirect_uri: data[:redirect_uri] || data['redirect_uri'],
      state: data[:state] || data['state'],
      resource: data[:resource] || data['resource'],
      scope: data[:scope] || data['scope'])
end

Instance Method Details

#to_h ⇒ Hash

Convert to hash for serialization

Returns:

  • (Hash) —

    Hash representation



612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
# File 'lib/mcp_client/auth.rb', line 612

def to_h
  hash = {
    code_verifier: @code_verifier,
    code_challenge: @code_challenge,
    code_challenge_method: @code_challenge_method
  }
  hash[:issuer] = @issuer if @issuer
  hash[:iss_parameter_supported] = @iss_parameter_supported unless @iss_parameter_supported.nil?
  hash[:client_id] = @client_id if @client_id
  hash[:redirect_uri] = @redirect_uri if @redirect_uri
  hash[:state] = @state if @state
  hash[:resource] = @resource if @resource
  hash[:scope] = @scope if @scope
  hash
end