Skip to content

Commit a0757ca

Browse files
authored
Merge pull request #678 from koic/stop_requiring_base64_in_the_oauth_client
Stop requiring `base64` in the OAuth client
2 parents 75067bb + eeee262 commit a0757ca

4 files changed

Lines changed: 49 additions & 6 deletions

File tree

‎lib/mcp/client/oauth/flow.rb‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# frozen_string_literal: true
22

3-
require "base64"
43
require "json"
54
require "openssl"
65
require "securerandom"
@@ -1653,7 +1652,7 @@ def reported_url(url)
16531652
def basic_auth_credentials(client_id, client_secret)
16541653
encoded_id = URI.encode_www_form_component(client_id)
16551654
encoded_secret = URI.encode_www_form_component(client_secret)
1656-
Base64.strict_encode64("#{encoded_id}:#{encoded_secret}")
1655+
["#{encoded_id}:#{encoded_secret}"].pack("m0")
16571656
end
16581657

16591658
def http_get(url)

‎lib/mcp/client/oauth/jwt_client_assertion.rb‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# frozen_string_literal: true
22

3-
require "base64"
43
require "json"
54
require "openssl"
65
require "securerandom"
@@ -118,8 +117,10 @@ def ecdsa_der_to_raw(der)
118117
end.join.then { |hex| [hex].pack("H*") }
119118
end
120119

120+
# base64url without padding (RFC 7515 Section 2), spelled with `pack` because `base64` is no longer
121+
# a default gem as of Ruby 3.4 and this gem does not depend on it.
121122
def base64url(data)
122-
Base64.urlsafe_encode64(data, padding: false)
123+
[data].pack("m0").tr("+/", "-_").delete("=")
123124
end
124125
end
125126
end

‎lib/mcp/client/oauth/pkce.rb‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# frozen_string_literal: true
22

3-
require "base64"
43
require "digest"
54
require "securerandom"
65

@@ -27,7 +26,9 @@ class << self
2726
# Generates a PKCE pair (code_verifier and S256 code_challenge).
2827
def generate
2928
verifier = SecureRandom.urlsafe_base64(64)
30-
challenge = Base64.urlsafe_encode64(Digest::SHA256.digest(verifier), padding: false)
29+
# base64url without padding (RFC 7636 Appendix A), spelled with `pack` because `base64` is no longer
30+
# a default gem as of Ruby 3.4 and this gem does not depend on it.
31+
challenge = [Digest::SHA256.digest(verifier)].pack("m0").tr("+/", "-_").delete("=")
3132

3233
{ code_verifier: verifier, code_challenge: challenge, code_challenge_method: "S256" }
3334
end
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# frozen_string_literal: true
2+
3+
require "test_helper"
4+
require "open3"
5+
6+
module MCP
7+
class Client
8+
module OAuth
9+
class Base64DependencyTest < ActiveSupport::TestCase
10+
# `base64` stopped being a default gem in Ruby 3.4 and the gemspec does not declare it, so under Bundler
11+
# an application that does not happen to get it from another gem cannot load it. Checked in a fresh process,
12+
# since this one has loaded it for the tests already, with the library refused the way Bundler refuses it.
13+
test "the client and the server-side OAuth classes load and encode without the base64 library" do
14+
lib = File.expand_path("../../../../lib", __dir__)
15+
script = <<~'RUBY'
16+
module RefuseBase64
17+
def require(name)
18+
raise LoadError, "cannot load such file -- base64" if name == "base64"
19+
20+
super
21+
end
22+
end
23+
Object.prepend(RefuseBase64)
24+
25+
require "mcp"
26+
MCP::Client
27+
pair = MCP::Client::OAuth::PKCE.generate
28+
abort("unexpected code_challenge: #{pair[:code_challenge]}") unless pair[:code_challenge].match?(/\A[A-Za-z0-9_-]{43}\z/)
29+
MCP::Client::OAuth::JWTClientAssertion
30+
MCP::Server::OAuth::ProtectedResourceMetadata.new(
31+
resource: "http://localhost:3000/mcp", authorization_servers: ["http://localhost:3000"],
32+
)
33+
abort("base64 was loaded") if defined?(Base64)
34+
RUBY
35+
_stdout, stderr, status = Open3.capture3({ "RUBYOPT" => nil }, RbConfig.ruby, "-I", lib, "-e", script)
36+
37+
assert_predicate status, :success?, stderr
38+
end
39+
end
40+
end
41+
end
42+
end

0 commit comments

Comments
 (0)