# ExMCP Security Guide

This guide covers security features and best practices for the ExMCP library.

## Overview

ExMCP provides comprehensive security features to ensure secure communication between MCP clients and servers. The security model is designed to be flexible while maintaining strong defaults.

## Security Features by Component

### Transport Security

| Feature | Streamable HTTP | stdio | Native Service Dispatcher |
|---------|-----------------|-------|---------------------------|
| Bearer Authentication | ✅ | ❌ | Via process validation |
| API Key Authentication | ✅ | ❌ | Via process validation |
| Basic Authentication | ✅ | ❌ | ❌ |
| Custom Headers | ✅ | ❌ | ❌ |
| Origin Validation | ✅ | ❌ | N/A |
| CORS Headers | ✅ | ❌ | N/A |
| TLS/SSL | ✅ | ❌ | Via Erlang distribution* |
| Mutual TLS | ✅ | ❌ | ❌ |
| OAuth 2.1 | ✅ | ❌ | Via process validation |

*Native Service Dispatcher uses Erlang distribution security between nodes

## Authentication Methods

### Bearer Token Authentication

Used for OAuth2 and JWT tokens:

```elixir
security = %{
  auth: {:bearer, "your-bearer-token"}
}

{:ok, client} = ExMCP.Client.start_link(
  transport: :http,
  url: "https://api.example.com",
  security: security
)
```

### API Key Authentication

For API key-based authentication:

```elixir
# Default header (X-API-Key)
security = %{
  auth: {:api_key, "your-api-key", []}
}

# Custom header
security = %{
  auth: {:api_key, "your-api-key", header: "X-Custom-API-Key"}
}
```

### Basic Authentication

HTTP Basic Authentication:

```elixir
security = %{
  auth: {:basic, "username", "password"}
}
```

### Custom Headers

For custom authentication schemes:

```elixir
security = %{
  auth: {:custom, [
    {"X-Auth-Token", "token"},
    {"X-Request-ID", "request-id"}
  ]}
}
```

### OAuth 2.1 Authentication

For OAuth 2.1 authentication flows:

```elixir
# Client credentials with client secret
{:ok, token_response} = ExMCP.Authorization.OAuthFlow.client_credentials_flow(%{
  client_id: "my-client",
  client_secret: "my-secret",
  token_endpoint: "https://auth.example.com/token"
})

# Then use the token for authentication
security = %{
  auth: {:oauth2, token_response}
}
```

### JWT Client Authentication (private_key_jwt)

For machine-to-machine auth using JWT client assertions (RFC 7523):

```elixir
# Load private key and authenticate with JWT instead of client secret
{:ok, private_key} = ExMCP.Authorization.JWT.load_key({:pem_file, "/path/to/key.pem"})

{:ok, token_response} = ExMCP.Authorization.OAuthFlow.client_credentials_jwt_flow(%{
  client_id: "my-client",
  private_key: private_key,
  token_endpoint: "https://auth.example.com/token",
  scopes: ["mcp:read", "mcp:write"]
})
```

### Enterprise-Managed Authorization (ID-JAG)

For enterprise SSO using ID-JAG (Identity JWT Authorization Grant):

```elixir
# Step 1: Authenticate user with enterprise IdP via OIDC (obtain id_token)
# Step 2-3: Exchange ID token for access token via ID-JAG flow
{:ok, access_token} = ExMCP.Authorization.EnterpriseFlow.execute(%{
  id_token: id_token_from_oidc,
  idp_token_endpoint: "https://idp.enterprise.com/token",
  as_issuer: "https://auth.example.com",
  resource_url: "https://mcp.example.com",
  client_id: "my-client"
})
```

Server-side ID-JAG validation:

```elixir
# Validate an incoming ID-JAG in a JWT bearer grant
{:ok, result} = ExMCP.Authorization.IdJagHandler.handle_grant(
  assertion: id_jag_token,
  expected_audience: "https://auth.example.com",
  expected_resource: "https://mcp.example.com",
  trusted_idps: %{
    "https://idp.enterprise.com" => %{
      jwks_uri: "https://idp.enterprise.com/.well-known/jwks.json"
    }
  }
)
```

## Transport-Specific Security

### Streamable HTTP Transport Security

The Streamable HTTP transport (with Server-Sent Events) supports the most comprehensive security features:

```elixir
security = %{
  # Authentication
  auth: {:bearer, "token"},
  
  # Origin validation
  validate_origin: true,
  allowed_origins: ["https://app.example.com"],
  
  # CORS configuration
  cors: %{
    allowed_origins: ["https://app.example.com"],
    allowed_methods: ["GET", "POST"],
    allowed_headers: ["Content-Type", "Authorization"],
    allow_credentials: true,
    max_age: 3600
  },
  
  # Custom headers
  headers: [
    {"X-Client-Version", "1.0.0"},
    {"X-Request-Source", "mobile-app"}
  ],
  
  # Include standard security headers
  include_security_headers: true,
  
  # TLS configuration
  tls: %{
    verify: :verify_peer,
    cacerts: :public_key.cacerts_get(),
    versions: [:"tlsv1.2", :"tlsv1.3"],
    cert: cert_data,  # For mutual TLS
    key: key_data     # For mutual TLS
  }
}
```


### Native Service Dispatcher Security

The Native Service Dispatcher (ExMCP.Native) provides high-performance communication between Elixir services within the same cluster. Security is handled at the Erlang distribution and process levels:

```elixir
# Services can implement their own authentication
defmodule MySecureService do
  use ExMCP.Service, name: :secure_service
  
  def init(args) do
    # Initialize with expected tokens
    {:ok, %{authorized_tokens: MapSet.new(args[:tokens] || [])}}
  end
  
  def handle_mcp_request(method, params, state) do
    # Validate authentication token from metadata
    case Map.get(params, "_meta", %{}) |> Map.get("auth_token") do
      nil -> 
        {:error, %{"code" => -32001, "message" => "Authentication required"}, state}
      
      token ->
        if MapSet.member?(state.authorized_tokens, token) do
          # Process authenticated request
          handle_authenticated_request(method, params, state)
        else
          {:error, %{"code" => -32002, "message" => "Invalid token"}, state}
        end
    end
  end
end

# Client usage with authentication
{:ok, result} = ExMCP.Native.call(
  :secure_service,
  "method_name",
  %{"data" => "value"},
  meta: %{"auth_token" => "secret-token"}
)
```

#### Erlang Distribution Security

For production deployments, secure the Erlang distribution layer:

```elixir
# 1. Set a strong cookie in your release configuration
# config/runtime.exs
config :my_app,
  erlang_cookie: System.fetch_env!("ERLANG_COOKIE")

# 2. Use TLS for distribution (in vm.args)
# -proto_dist inet_tls
# -ssl_dist_optfile /path/to/ssl_dist.conf

# 3. Configure node names to use fully qualified domain names
# -name myapp@secure.internal.network
```

#### Native Service Security Best Practices

1. **Node Security**: Use strong Erlang cookies and consider TLS distribution
2. **Process Isolation**: Each service runs in its own supervised process
3. **Network Isolation**: Deploy clusters on private networks
4. **Authentication**: Implement service-level authentication for sensitive operations
5. **Monitoring**: Use OTP supervision and monitoring for security events

## Security Best Practices

### 1. Always Use TLS in Production

```elixir
# Good - uses HTTPS/WSS
{:ok, client} = ExMCP.Client.start_link(
  transport: :http,
  url: "https://api.example.com",
  security: %{auth: {:bearer, token}}
)

# Bad - unencrypted HTTP
{:ok, client} = ExMCP.Client.start_link(
  transport: :http,
  url: "http://api.example.com",  # Insecure!
  security: %{auth: {:bearer, token}}
)
```

### 2. Validate Origins for Web-Based Clients

```elixir
security = %{
  validate_origin: true,
  allowed_origins: [
    "https://app.mycompany.com",
    "https://staging.mycompany.com"
  ]
}
```

### 3. Use Strong Authentication

```elixir
# Good - strong token
security = %{
  auth: {:bearer, "eyJhbGciOiJIUzI1NiIs..."}  # JWT or strong token
}

# Bad - weak token
security = %{
  auth: {:bearer, "simple-password"}  # Avoid simple passwords
}
```

### 4. Configure CORS Properly

```elixir
cors = %{
  # Specific origins instead of wildcard
  allowed_origins: ["https://app.example.com"],
  
  # Only required methods
  allowed_methods: ["GET", "POST"],
  
  # Only required headers
  allowed_headers: ["Content-Type", "Authorization"],
  
  # Enable credentials only if needed
  allow_credentials: true
}
```

### 5. Use Certificate Validation

```elixir
tls = %{
  # Always verify certificates in production
  verify: :verify_peer,
  
  # Use system CA certificates
  cacerts: :public_key.cacerts_get(),
  
  # Use modern TLS versions
  versions: [:"tlsv1.2", :"tlsv1.3"]
}
```

## Error Handling

Security-related errors are returned with descriptive error tuples:

```elixir
case ExMCP.Client.start_link(transport: :http, url: url, security: security) do
  {:ok, client} ->
    # Success
    client
    
  {:error, :invalid_auth_config} ->
    # Invalid authentication configuration
    handle_auth_error()
    
  {:error, :origin_not_allowed} ->
    # Origin validation failed
    handle_origin_error()
    
  {:error, :connection_refused} ->
    # Server refused connection (possibly auth failure)
    handle_connection_error()
    
  {:error, reason} ->
    # Other errors
    handle_generic_error(reason)
end
```

## Testing Security Configuration

ExMCP provides utilities for testing security configurations:

```elixir
# Validate security configuration
case ExMCP.Security.validate_config(security_config) do
  :ok -> 
    # Configuration is valid
    proceed_with_connection()
    
  {:error, reason} ->
    # Configuration is invalid
    fix_configuration(reason)
end

# Test authentication headers
headers = ExMCP.Security.build_auth_headers(security_config)
assert {"Authorization", "Bearer token"} in headers
```

## Security Considerations

### 1. Token Management

- Store tokens securely (encrypted at rest)
- Rotate tokens regularly
- Use short-lived tokens when possible
- Implement token refresh mechanisms

### 2. Network Security

- Always use TLS for production traffic
- Consider using mutual TLS for high-security environments
- Implement rate limiting at the network level
- Use VPNs or private networks when possible

### 3. Process Security (BEAM Transport)

- Use Erlang node cookies for basic node authentication
- Consider network-level isolation for distributed nodes
- Monitor process connections and authentication attempts

### 4. Error Handling

- Don't leak sensitive information in error messages
- Log authentication failures for monitoring
- Implement exponential backoff for failed auth attempts

### 5. Monitoring and Auditing

- Log all authentication attempts
- Monitor for unusual connection patterns
- Track API usage and rate limits
- Set up alerts for security events

## Example: Complete Secure Setup

Here's a complete example of a secure MCP client setup:

```elixir
defmodule SecureMCPClient do
  def start_secure_client do
    # Load configuration securely
    token = System.get_env("MCP_AUTH_TOKEN") || load_from_secure_store()
    server_url = System.get_env("MCP_SERVER_URL") || "https://api.company.com"
    
    security = %{
      # Strong authentication
      auth: {:bearer, token},
      
      # Origin validation
      validate_origin: true,
      allowed_origins: ["https://app.company.com"],
      
      # Secure headers
      headers: [
        {"X-Client-Version", Application.spec(:my_app, :vsn)},
        {"X-Request-ID", generate_request_id()}
      ],
      
      # TLS configuration
      tls: %{
        verify: :verify_peer,
        cacerts: :public_key.cacerts_get(),
        versions: [:"tlsv1.3"]
      },
      
      # CORS for web clients
      cors: %{
        allowed_origins: ["https://app.company.com"],
        allowed_methods: ["GET", "POST"],
        allow_credentials: true
      }
    }
    
    # Validate configuration
    case ExMCP.Security.validate_config(security) do
      :ok ->
        ExMCP.Client.start_link(
          transport: :http,
          url: server_url,
          security: security
        )
        
      {:error, reason} ->
        {:error, {:security_config_invalid, reason}}
    end
  end
  
  defp load_from_secure_store do
    # Implement secure token loading
    # e.g., from encrypted file, HSM, or vault
  end
  
  defp generate_request_id do
    # Generate unique request ID for tracing
    :crypto.strong_rand_bytes(16) |> Base.encode64()
  end
end
```

## Troubleshooting

### Common Security Issues

1. **Authentication Failures**
   - Verify token format and validity
   - Check server authentication requirements
   - Ensure headers are properly formatted

2. **Origin Validation Errors**
   - Verify allowed origins configuration
   - Check client origin header
   - Ensure proper CORS setup

3. **TLS/Certificate Issues**
   - Verify certificate validity
   - Check CA certificate configuration
   - Ensure proper hostname verification

4. **Connection Refused**
   - Check authentication credentials
   - Verify server security requirements
   - Check network connectivity and firewall rules

### Debugging Security Issues

Enable debug logging to troubleshoot security issues:

```elixir
# Enable debug logging
Logger.configure(level: :debug)

# Check security configuration
IO.inspect(ExMCP.Security.validate_config(security))

# Check generated headers
IO.inspect(ExMCP.Security.build_security_headers(security))
```

## Migration Guide

### Upgrading from Unsecured Connections

1. **Add authentication**:
   ```elixir
   # Before
   {:ok, client} = ExMCP.Client.start_link(transport: :http, url: url)
   
   # After
   {:ok, client} = ExMCP.Client.start_link(
     transport: :http, 
     url: url, 
     security: %{auth: {:bearer, token}}
   )
   ```

2. **Enable TLS**:
   ```elixir
   # Change HTTP to HTTPS
   url = "https://api.example.com"  # was "http://..."
   ```

3. **Add origin validation**:
   ```elixir
   security = %{
     auth: {:bearer, token},
     validate_origin: true,
     allowed_origins: ["https://yourapp.com"]
   }
   ```

## Summary

ExMCP provides comprehensive security features across all transports:

- **Streamable HTTP Transport**: Full authentication, TLS, CORS, and origin validation support
- **stdio Transport**: Limited security (process isolation only)
- **Native Service Dispatcher**: Process-level security with Erlang distribution protection

Choose the appropriate transport based on your security requirements. For maximum security, use the Streamable HTTP transport with TLS and proper authentication.
