Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 189 additions & 15 deletions docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,16 @@ Security is a critical aspect of any application that exposes functionality thro

- [DNS Rebinding Protection](#dns-rebinding-protection)
- [Authentication](#authentication)
- [HTTPS and SSL](#https-and-ssl)
- [Enabling Authentication](#enabling-authentication)
- [Authentication Strategies](#authentication-strategies)
- [Token-based Authentication](#1-token-based-authentication-default)
- [Custom Authentication Headers](#custom-authentication-headers)
- [Proc-based Authentication](#2-proc-based-authentication)
- [HTTP Basic Authentication](#3-http-basic-authentication)
- [Authentication Exemptions](#authentication-exemptions)
- [Authentication Environment Variables](#authentication-environment-variables)
- [Best Practices](#best-practices)
- [Additional Resources](#additional-resources)

## DNS Rebinding Protection

Expand Down Expand Up @@ -65,29 +73,181 @@ When a request arrives at the MCP endpoint, the RackTransport middleware:

## Authentication

Fast MCP supports token-based authentication for all connections to ensure only authorized clients can access your MCP server.
Fast MCP supports multiple authentication strategies to ensure only authorized clients can access your MCP server.

### Basic Authentication
### Enabling Authentication

To enable authentication, use the `authenticated_rack_middleware` method:
To enable authentication, set the `authenticate` option to `true` and provide the appropriate authentication options:

```ruby
# Enable authentication
FastMcp.authenticated_rack_middleware(app,
auth_token: 'your-secret-token',
# other options...
authenticate: true,
auth_options: {
# Authentication configuration options...
}
)
```

In Rails applications, you can enable authentication in the initializer:

```ruby
FastMcp.mount_in_rails(
Rails.application,
authenticate: true,
auth_options: {
# Authentication configuration options...
}
)
```

### Authentication Strategies

Fast MCP supports three authentication strategies, configured within the `auth_options` hash:

#### 1. Token-based Authentication (Default)

The simplest authentication strategy that validates a token from the request header:

```ruby
FastMcp.authenticated_rack_middleware(app,
authenticate: true,
auth_options: {
auth_strategy: :token, # This is the default if not specified
auth_token: 'your-secret-token',
auth_header: 'Authorization' # Optional, defaults to 'Authorization'
}
)
```

You can also use environment variables for your token:
```ruby
# Set MCP_AUTH_TOKEN in your environment
FastMcp.authenticated_rack_middleware(app,
authenticate: true,
auth_options: {
auth_strategy: :token
# Will use ENV['MCP_AUTH_TOKEN'] and ENV['MCP_AUTH_HEADER'] (defaults to 'Authorization')
}
)
```

### Custom Authentication Headers

You can configure the header name used for authentication:
By default, Fast MCP uses the `Authorization` header for token-based authentication, but you can configure it to use any custom header. This is particularly useful for:

1. Integration with API gateway services
2. Adding an additional security layer (security through obscurity)
3. Supporting multiple authentication schemes

#### Using X-API-Key Header

A common pattern for API authentication is to use the `X-API-Key` header instead of the standard `Authorization` header:

```ruby
FastMcp.authenticated_rack_middleware(app,
auth_token: 'your-secret-token',
auth_header_name: 'X-API-Key', # Default is 'Authorization'
# other options...
authenticate: true,
auth_options: {
auth_strategy: :token,
auth_token: 'your-secret-token',
auth_header: 'X-API-Key' # Use X-API-Key header instead of Authorization
}
)
```

With this configuration, clients should send the API key directly in the header without the "Bearer" prefix:

```
X-API-Key: your-secret-token
```

#### Environment Variable Configuration

You can also set the custom header using environment variables:

```ruby
# In your environment:
# MCP_AUTH_HEADER=X-API-Key
# MCP_AUTH_TOKEN=your-secret-token

FastMcp.authenticated_rack_middleware(app,
authenticate: true,
auth_options: {
auth_strategy: :token
# Will use ENV['MCP_AUTH_HEADER'] and ENV['MCP_AUTH_TOKEN']
}
)
```

#### With Proc-based Authentication

When using proc-based authentication, remember to access the correct header in your custom logic:

```ruby
FastMcp.authenticated_rack_middleware(app,
authenticate: true,
auth_options: {
auth_strategy: :proc,
auth_proc: ->(request) {
# Access X-API-Key header
api_key = request.get_header('HTTP_X_API_KEY')
# Validate the API key
valid_keys = ['key1', 'key2', 'key3']
valid_keys.include?(api_key)
}
}
)
```

#### 2. Proc-based Authentication

For more complex authentication scenarios, you can use a proc that receives the entire request object:

```ruby
FastMcp.authenticated_rack_middleware(app,
authenticate: true,
auth_options: {
auth_strategy: :proc,
auth_proc: ->(request) {
# Your custom authentication logic here
# Access the full request object for context
token = request.get_header('HTTP_AUTHORIZATION')&.gsub(/^Bearer\s+/i, '')
User.find_by(api_token: token).present?
}
}
)
```

This allows you to:
- Check tokens against your database
- Implement expiring tokens
- Validate user permissions
- Access request parameters for context-specific authentication
- Implement custom header or cookie-based authentication

#### 3. HTTP Basic Authentication

For applications that prefer username/password authentication:

```ruby
FastMcp.authenticated_rack_middleware(app,
authenticate: true,
auth_options: {
auth_strategy: :http_basic,
auth_user: 'admin', # Username to accept
auth_password: 'secret' # Password to accept
}
)
```

You can also set these values using environment variables:
```ruby
# Set MCP_AUTH_USER and MCP_AUTH_PASSWORD in your environment
FastMcp.authenticated_rack_middleware(app,
authenticate: true,
auth_options: {
auth_strategy: :http_basic
# Will use ENV['MCP_AUTH_USER'] and ENV['MCP_AUTH_PASSWORD']
}
)
```

Expand All @@ -97,20 +257,34 @@ Some paths can be exempted from authentication:

```ruby
FastMcp.authenticated_rack_middleware(app,
auth_token: 'your-secret-token',
auth_exempt_paths: ['/health-check'], # Paths that don't require authentication
# other options...
authenticate: true,
auth_options: {
auth_strategy: :token,
auth_token: 'your-secret-token',
auth_exempt_paths: ['/health-check', '/mcp/public'] # Paths that don't require authentication
}
)
```

### Authentication Environment Variables

For security best practices, you can use environment variables for sensitive authentication information:

| Environment Variable | Description | Default |
|---------------------|-------------|---------|
| `MCP_AUTH_TOKEN` | The token for token-based authentication | None (Required) |
| `MCP_AUTH_HEADER` | The header name for token-based auth | `Authorization` |
| `MCP_AUTH_USER` | The username for HTTP Basic authentication | None (Required) |
| `MCP_AUTH_PASSWORD` | The password for HTTP Basic authentication | None (Required) |

## Best Practices

Here are some best practices to enhance the security of your MCP server:

1. **Always validate Origin headers** (enabled by default)
2. **Use authentication** for all MCP endpoints in production
3. **Deploy behind HTTPS** in production environments
4. **Keep your auth_token secret** and rotate it regularly
4. **Keep your auth credentials in environment variables** rather than in code
5. **Implement proper error handling** to avoid leaking sensitive information
6. **Validate inputs thoroughly** in your tool implementations
7. **Implement rate limiting** for MCP endpoints to prevent abuse
Expand Down
18 changes: 17 additions & 1 deletion lib/fast_mcp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,15 @@ def self.rack_middleware(app, options = {})
# @param options [Hash] Options for the middleware
# @option options [String] :name The name of the server
# @option options [String] :version The version of the server
# @option options [Boolean] :authenticate Whether to enable authentication
# @option options [Symbol] :authentication_strategy The authentication strategy to use (:token, :proc, or :http_basic)
# @option options [String] :auth_token The authentication token
# @option options [Proc] :auth_proc A proc for custom authentication with behavior dependent on the strategy:
# - For :token strategy: proc takes (token, request) and returns a boolean
# - For :proc strategy: proc takes (token, request) and returns a boolean
# - For :http_basic strategy: proc takes (username, password, request) and returns a boolean
# @option options [String] :auth_header_name Custom header name for authentication (default: 'Authorization')
# @option options [Array<String>] :auth_exempt_paths Paths that don't require authentication
# @option options [Array<String,Regexp>] :allowed_origins List of allowed origins for DNS rebinding protection
# @yield [server] A block to configure the server
# @yieldparam server [FastMcp::Server] The server to configure
Expand Down Expand Up @@ -125,8 +133,15 @@ def self.register_resources(*resources)
# @option options [String] :messages_route The route for the messages endpoint
# @option options [String] :sse_route The route for the SSE endpoint
# @option options [Logger] :logger The logger to use
# @option options [Boolean] :authenticate Whether to use authentication
# @option options [Boolean] :authenticate Whether to enable authentication
# @option options [Symbol] :authentication_strategy The authentication strategy to use (:token, :proc, or :http_basic)
# @option options [String] :auth_token The authentication token
# @option options [Proc] :auth_proc A proc for custom authentication with behavior dependent on the strategy:
# - For :token strategy: proc takes (token, request) and returns a boolean
# - For :proc strategy: proc takes (token, request) and returns a boolean
# - For :http_basic strategy: proc takes (username, password, request) and returns a boolean
# @option options [String] :auth_header_name Custom header name for authentication (default: 'Authorization')
# @option options [Array<String>] :auth_exempt_paths Paths that don't require authentication
# @option options [Array<String,Regexp>] :allowed_origins List of allowed origins for DNS rebinding protection
# @yield [server] A block to configure the server
# @yieldparam server [FastMcp::Server] The server to configure
Expand All @@ -144,6 +159,7 @@ def self.mount_in_rails(app, options = {})

options[:logger] = logger
options[:allowed_origins] = allowed_origins
options[:authenticate] = authenticate # Ensure authenticate flag is passed to middleware

# Create or get the server
self.server = FastMcp::Server.new(name: name, version: version, logger: logger)
Expand Down
41 changes: 39 additions & 2 deletions lib/generators/fast_mcp/install/templates/fast_mcp_initializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,45 @@
sse_route: 'sse' # This is the default route for the SSE endpoint
# Add allowed origins below, it defaults to Rails.application.config.hosts
# allowed_origins: ['localhost', '127.0.0.1', 'example.com', /.*\.example\.com/],
# authenticate: true, # Uncomment to enable authentication
# auth_token: 'your-token', # Required if authenticate: true

# Authentication Configuration
# --------------------------
# authenticate: true, # Uncomment to enable authentication
# auth_options: {
# # Choose one of the authentication strategies below:
#
# # 1. Token-based authentication (default)
# auth_strategy: :token,
# auth_token: 'your-secret-token',
# # auth_header: 'Authorization', # Optional, defaults to 'Authorization'
# # Using X-API-Key instead of Authorization header:
# # auth_header: 'X-API-Key', # Clients should send 'X-API-Key: your-secret-token'
#
# # 2. Proc-based authentication
# # auth_strategy: :proc,
# # auth_proc: ->(request) {
# # # Your custom authentication logic here
# # # The entire request object is available
# # token = request.get_header('HTTP_AUTHORIZATION')&.gsub(/^Bearer\s+/i, '')
# # User.find_by(api_token: token).present?
# # },
#
# # 3. HTTP Basic Authentication
# # auth_strategy: :http_basic,
# # auth_user: 'admin',
# # auth_password: 'secret',
#
# # Additional Authentication Options
# # auth_exempt_paths: ['/health-check', '/mcp/public'], # Paths that don't require authentication
# },

# Environment Variables for Authentication
# ---------------------------------------
# Instead of hardcoding authentication details, you can use environment variables:
# - MCP_AUTH_TOKEN: The token for token-based authentication
# - MCP_AUTH_HEADER: The header name for token-based auth (defaults to 'Authorization')
# - MCP_AUTH_USER: The username for HTTP Basic authentication
# - MCP_AUTH_PASSWORD: The password for HTTP Basic authentication
) do |server|
Rails.application.config.after_initialize do
# FastMcp will automatically discover and register:
Expand Down
Loading