Class: Woods::MCP::BearerAuth

Inherits:
Object
  • Object
show all
Defined in:
lib/woods/mcp/bearer_auth.rb

Overview

Rack middleware that rejects requests lacking a matching bearer token.

Uses Rack::Utils.secure_compare for constant-time comparison to avoid leaking token bytes via response-time side channels.

Constant Summary collapse

UNAUTHORIZED_BODY =
{ jsonrpc: '2.0', error: { code: -32_001, message: 'Unauthorized' }, id: nil }.to_json.freeze
MIN_TOKEN_LENGTH =

Bearer tokens shorter than this are rejected at construction time. Matches OWASP “session ID entropy” guidance (>= 128 bits ≈ 32 hex chars).

32

Instance Method Summary collapse

Constructor Details

#initialize(app, token:) ⇒ BearerAuth

Returns a new instance of BearerAuth.

Raises:

  • (ArgumentError)


19
20
21
22
23
24
25
26
27
28
29
# File 'lib/woods/mcp/bearer_auth.rb', line 19

def initialize(app, token:)
  raise ArgumentError, 'token must be a non-empty string' if token.nil? || token.empty?
  if token.to_s.length < MIN_TOKEN_LENGTH
    raise ArgumentError,
          "bearer token must be at least #{MIN_TOKEN_LENGTH} characters " \
          "(got #{token.to_s.length}); generate with `SecureRandom.hex(32)`"
  end

  @app = app
  @token = token.to_s
end

Instance Method Details

#call(env) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/woods/mcp/bearer_auth.rb', line 31

def call(env)
  header = env['HTTP_AUTHORIZATION'].to_s
  presented = header.start_with?('Bearer ') ? header.sub(/\ABearer /, '') : nil

  if presented && Rack::Utils.secure_compare(@token, presented)
    @app.call(env)
  else
    [401,
     { 'content-type' => 'application/json', 'www-authenticate' => 'Bearer realm="woods-mcp-http"' },
     [UNAUTHORIZED_BODY]]
  end
end