Class: Woods::MCP::BearerAuth
- Inherits:
-
Object
- Object
- Woods::MCP::BearerAuth
- 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
- #call(env) ⇒ Object
-
#initialize(app, token:) ⇒ BearerAuth
constructor
A new instance of BearerAuth.
Constructor Details
#initialize(app, token:) ⇒ BearerAuth
Returns a new instance of BearerAuth.
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 |