Repository: IBM/mcp-context-forge

Issue:
[Feature Request]: Option to completely remove Bearer token auth to MCP gateway
###Feature  Summary
Briefly describe the issue or unexpected behavior.

**I am doing auth using a frontend proxy that has proper OAuth and federation capabilities, thus I will not be leveraging any of the manual JWT generation on the MCP gateway side.**


---

### 🧩 Affected Component
Select the area of the project impacted:

- [ X] `mcpgateway` - API
- [ ] `mcpgateway` - UI (admin panel)
- [] `mcpgateway.wrapper` - stdio wrapper
- [ X] Federation or Transports
- [ ] CLI, Makefiles, or shell scripts
- [X ] Container setup (Docker/Podman/Compose)
- [ ] Other (explain below)

---

### 🔁 Steps to Reproduce

**1. Add MCP Server
2. Set Environment Variable to AUTH_REQUIRED: false
(This currently only disables the admin login, there isn't any separate environment variable to disable MCP Client Auth completely)**

---

### 🤔 Expected Behavior
What should have happened instead?

**We should be allowed to set an environment variable to disable MCP Client Auth**

---

### 📓 Logs / Error Output
Paste any relevant stack traces or logs here.
⚠️ **Do not paste secrets, credentials, or tokens.**

---

### 🧠 Environment Info
You can retrieve most of this from the `/version` endpoint.

| Key | Value |
|-----|-------|
| Version or commit | `e.g. v0.9.0 or main@a1b2c3d` |
| Runtime | `e.g. Python 3.11, Gunicorn` |
| Platform / OS | `e.g. Ubuntu 22.04, macOS` |
| Container | `e.g. Docker, Podman, none` |

Running on Docker

<img width="1293" height="782" alt="Image" src="https://github.com/user-attachments/assets/e7efb0f4-0fe8-43e6-9a5b-bfe7c9408311" />


### 🧩 Additional Context (optional)
Add any configuration details, flags, or related issues.



Discussion:
Hi, converted this to an issue to add new config flags. Please me know if the assessment below makes sense.

## Current Authentication Architecture

The MCP Gateway currently implements two distinct authentication mechanisms:

### 1. Admin UI Authentication
- **Configuration**: `BASIC_AUTH_USER` and `BASIC_AUTH_PASSWORD`
- **Scope**: Web-based admin panel at `/admin`
- **Flow**: HTTP Basic Auth → JWT cookie for session
- **Purpose**: Protect administrative interface

### 2. MCP Client Authentication  
- **Configuration**: `JWT_SECRET_KEY` for token signing
- **Scope**: All API endpoints, SSE connections, WebSocket connections
- **Flow**: Bearer token in Authorization header or jwt_token cookie
- **Token Generation**: `python3 -m mcpgateway.utils.create_jwt_token`
- **Purpose**: Authenticate MCP client connections

## The Problem

Currently, the `AUTH_REQUIRED` environment variable acts as a global switch that affects both authentication types, but doesn't fully disable JWT validation:

- When `AUTH_REQUIRED=true` (default): Both admin UI and MCP clients require authentication
- When `AUTH_REQUIRED=false`: Authentication becomes optional but JWT validation still occurs if tokens are provided

**There is no way to completely bypass JWT bearer token authentication for MCP clients.**

## Your Use Case (Issue #705)

You have a legitimate enterprise deployment pattern:

```
User → Frontend Auth Proxy (OAuth/SAML) → MCP Gateway → MCP Servers
```

Your setup:
1. Frontend proxy (e.g., OAuth2 Proxy, Authelia, Cloudflare Access) handles all authentication
2. Proxy validates users against corporate identity provider (Okta, Auth0, Azure AD)
3. Proxy adds trusted headers like `X-Authenticated-User` or `X-Remote-User`
4. MCP Gateway should trust the proxy's authentication decision

## Why Current Setup Doesn't Work

Even with `AUTH_REQUIRED=false`:
- Gateway still expects JWT tokens in the Authorization header
- Gateway validates tokens if provided (checking signature, expiration)
- Clients must generate and manage JWT tokens despite proxy authentication
- Adds unnecessary complexity when authentication is already handled upstream

## Common Enterprise Patterns This Blocks

1. **API Gateway Authentication**: Kong, Traefik, AWS API Gateway handle auth
2. **Identity-Aware Proxies**: Google IAP, Cloudflare Access, Tailscale
3. **Service Mesh**: Istio/Linkerd with mTLS and identity propagation
4. **Corporate SSO**: Direct integration with SAML/OIDC providers

## Proposed Solution

Add a new configuration option to completely disable MCP client JWT validation:

### Option 1: Separate MCP Client Auth Toggle
```bash
# New environment variable
MCP_CLIENT_AUTH_ENABLED=false   # Completely skip JWT validation for MCP operations
AUTH_REQUIRED=true              # Still protect admin UI with basic auth
```

### Option 2: Auth Mode Selection
```bash
# Authentication mode
AUTH_MODE=proxy        # Options: jwt (default), proxy, none
TRUST_PROXY_HEADERS=true
PROXY_USER_HEADER=X-Authenticated-User
```

### Option 3: Granular Auth Control
```bash
# Separate controls for each auth type
ADMIN_UI_AUTH_ENABLED=true
MCP_CLIENT_AUTH_ENABLED=false
REQUIRE_TOKEN_VALIDATION=false
```

## Implementation Considerations

### Security Implications
- **CRITICAL**: This should only be enabled when MCP Gateway is behind a trusted proxy. Likely something we should flag in #534 
- Document clearly that disabling MCP auth removes a security layer
- Consider requiring explicit proxy trust configuration
- Maybe require setting `TRUST_PROXY=true` as a safety check

### Code Changes Needed

1. **config.py**: Add new configuration variable(s)
2. **verify_credentials.py**: Modify `require_auth()` to skip validation when disabled
3. **main.py**: Update middleware to respect new settings
4. **WebSocket endpoint**: Currently has no auth - needs to be addressed

### Header Passthrough Integration
- When proxy auth is enabled, automatically trust and use proxy headers
- Map proxy user header to the internal user context
- Ensure `X-Authenticated-User` or similar is available to downstream services

## Benefits of Supporting This

1. **Enterprise Ready**: Aligns with standard enterprise deployment patterns
2. **Simplified Operations**: No need to manage JWT tokens when using proxy auth
3. **Better Integration**: Works seamlessly with existing auth infrastructure
4. **Flexibility**: Supports multiple authentication strategies

## Recommended Approach

1. Implement `MCP_CLIENT_AUTH_ENABLED` as a separate toggle
2. Keep `AUTH_REQUIRED` for backward compatibility (controls admin UI)
3. Add safety checks to ensure proxy configuration when auth is disabled
4. Document the security implications clearly
5. Add examples for common proxy configurations (OAuth2 Proxy, Authelia, etc.)

## Migration Path

For existing users:
- Default behavior unchanged (`MCP_CLIENT_AUTH_ENABLED=true`)
- `AUTH_REQUIRED=false` continues to work as before
- New option is opt-in for proxy authentication scenarios

## Testing Requirements

1. Test with `MCP_CLIENT_AUTH_ENABLED=false` and no proxy (should warn)
2. Test with proxy headers being correctly passed through
3. Ensure admin UI auth still works independently
4. Verify WebSocket connections respect the new setting
5. Test federation scenarios with mixed auth modes

## Documentation Updates Needed

1. Add "Proxy Authentication" section to deployment docs
2. Create example configurations for common proxies
3. Add security best practices guide
4. Update environment variable reference
5. Add architecture diagram showing proxy auth flow
@CalebBartleMAA PR #710 is now available for review.
This is perfect @crivetimihai, thank you for taking the time to review this in detail.



Patch:
diff --git a/.env.example b/.env.example
index 35503587..f5a87bf9 100644
--- a/.env.example
+++ b/.env.example
@@ -100,6 +100,15 @@ TOKEN_EXPIRY=10080
 # Require all JWT tokens to have expiration claims (true or false)
 REQUIRE_TOKEN_EXPIRATION=false
 
+# MCP Client Authentication (for proxy authentication scenarios)
+# Disable JWT authentication for MCP operations (use with caution!)
+MCP_CLIENT_AUTH_ENABLED=true
+
+# Trust proxy authentication headers (only set true when behind auth proxy)
+TRUST_PROXY_AUTH=false
+
+# Header containing authenticated username from proxy
+PROXY_USER_HEADER=X-Authenticated-User
 
 # Used to derive an AES encryption key for secure auth storage
 # Must be a non-empty string (e.g. passphrase or random secret)
diff --git a/docs/docs/deployment/proxy-auth.md b/docs/docs/deployment/proxy-auth.md
new file mode 100644
index 00000000..0e456cf5
--- /dev/null
+++ b/docs/docs/deployment/proxy-auth.md
@@ -0,0 +1,294 @@
+# Proxy Authentication
+
+This guide explains how to configure MCP Gateway to work with authentication proxies like OAuth2 Proxy, Authelia, Cloudflare Access, or enterprise API gateways.
+
+## Overview
+
+When MCP Gateway is deployed behind an authentication proxy, you can disable its built-in JWT authentication and trust the proxy to handle user authentication. This is common in enterprise environments where authentication is centralized.
+
+## Architecture
+
+```
+User → Auth Proxy (OAuth/SAML) → MCP Gateway → MCP Servers
+         ↓
+    Identity Provider
+    (Okta, Auth0, Azure AD)
+```
+
+## Configuration
+
+### Environment Variables
+
+To enable proxy authentication, configure these environment variables:
+
+```bash
+# Disable JWT authentication for MCP operations
+MCP_CLIENT_AUTH_ENABLED=false
+
+# REQUIRED: Explicitly trust proxy authentication
+# Only set this when MCP Gateway is behind a trusted proxy!
+TRUST_PROXY_AUTH=true
+
+# Header containing the authenticated username from proxy
+# Default: X-Authenticated-User
+PROXY_USER_HEADER=X-Authenticated-User
+
+# Keep admin UI authentication enabled (optional)
+AUTH_REQUIRED=true
+```
+
+### Security Warning
+
+⚠️ **IMPORTANT**: Only disable MCP client authentication when MCP Gateway is deployed behind a trusted authentication proxy. Setting `MCP_CLIENT_AUTH_ENABLED=false` without `TRUST_PROXY_AUTH=true` will log a warning, as this removes a critical security layer.
+
+## Common Proxy Configurations
+
+### OAuth2 Proxy
+
+OAuth2 Proxy is a popular reverse proxy that provides authentication using OAuth2 providers.
+
+```yaml
+# docker-compose.yml
+services:
+  oauth2-proxy:
+    image: quay.io/oauth2-proxy/oauth2-proxy:latest
+    ports:
+      - "4180:4180"
+    environment:
+      OAUTH2_PROXY_CLIENT_ID: your-client-id
+      OAUTH2_PROXY_CLIENT_SECRET: your-client-secret
+      OAUTH2_PROXY_COOKIE_SECRET: your-cookie-secret
+      OAUTH2_PROXY_UPSTREAMS: http://mcp-gateway:4444
+      OAUTH2_PROXY_PASS_USER_HEADERS: true
+      OAUTH2_PROXY_SET_XAUTHREQUEST: true
+    
+  mcp-gateway:
+    image: ghcr.io/contingentai/mcp-gateway:latest
+    environment:
+      MCP_CLIENT_AUTH_ENABLED: false
+      TRUST_PROXY_AUTH: true
+      PROXY_USER_HEADER: X-Auth-Request-User
+```
+
+### Authelia
+
+Authelia is a complete authentication and authorization server.
+
+```yaml
+# Example Authelia forward auth configuration
+services:
+  authelia:
+    image: authelia/authelia
+    volumes:
+      - ./authelia:/config
+    environment:
+      TZ: America/New_York
+  
+  mcp-gateway:
+    image: ghcr.io/contingentai/mcp-gateway:latest
+    environment:
+      MCP_CLIENT_AUTH_ENABLED: false
+      TRUST_PROXY_AUTH: true
+      PROXY_USER_HEADER: Remote-User
+    labels:
+      - "traefik.http.routers.mcp.middlewares=authelia@docker"
+```
+
+### Cloudflare Access
+
+For Cloudflare Access, configure the gateway to read the authenticated user from Cloudflare's headers:
+
+```bash
+MCP_CLIENT_AUTH_ENABLED=false
+TRUST_PROXY_AUTH=true
+PROXY_USER_HEADER=Cf-Access-Authenticated-User-Email
+```
+
+### AWS API Gateway
+
+When using AWS API Gateway with Lambda authorizers:
+
+```bash
+MCP_CLIENT_AUTH_ENABLED=false
+TRUST_PROXY_AUTH=true
+PROXY_USER_HEADER=X-Authenticated-User
+```
+
+Configure your Lambda authorizer to add the authenticated username to the context:
+
+```python
+# Lambda authorizer example
+def lambda_handler(event, context):
+    # Validate token...
+    return {
+        'principalId': user_id,
+        'context': {
+            'authenticatedUser': user_email
+        }
+    }
+```
+
+## Header Passthrough
+
+When using proxy authentication, you may want to pass additional headers to downstream MCP servers:
+
+```bash
+# Enable header passthrough
+ENABLE_HEADER_PASSTHROUGH=true
+
+# Headers to pass through (JSON array)
+DEFAULT_PASSTHROUGH_HEADERS='["X-Tenant-Id", "X-Request-Id", "X-Authenticated-User"]'
+```
+
+## Kubernetes with Istio
+
+In a service mesh with Istio, you can use JWT validation at the mesh level:
+
+```yaml
+apiVersion: security.istio.io/v1beta1
+kind: RequestAuthentication
+metadata:
+  name: mcp-gateway-auth
+spec:
+  selector:
+    matchLabels:
+      app: mcp-gateway
+  jwtRules:
+  - issuer: "https://your-issuer.com"
+    jwksUri: "https://your-issuer.com/.well-known/jwks.json"
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: mcp-gateway-config
+data:
+  MCP_CLIENT_AUTH_ENABLED: "false"
+  TRUST_PROXY_AUTH: "true"
+  PROXY_USER_HEADER: "X-User-Id"
+```
+
+## Testing Proxy Authentication
+
+To test your proxy authentication setup:
+
+1. **Without proxy headers (should fail or return anonymous):**
+```bash
+curl http://localhost:4444/tools
+# Returns 401 or anonymous access depending on AUTH_REQUIRED
+```
+
+2. **With proxy headers:**
+```bash
+curl -H "X-Authenticated-User: john.doe@example.com" \
+     http://localhost:4444/tools
+# Should return tools list for authenticated user
+```
+
+3. **WebSocket with proxy auth:**
+```javascript
+const ws = new WebSocket('ws://localhost:4444/ws', {
+  headers: {
+    'X-Authenticated-User': 'john.doe@example.com'
+  }
+});
+```
+
+## Troubleshooting
+
+### Warning: MCP auth disabled without trust
+
+If you see this warning in logs:
+```
+WARNING - MCP client authentication is disabled but trust_proxy_auth is not set
+```
+
+**Solution**: Set `TRUST_PROXY_AUTH=true` to acknowledge you're using proxy authentication.
+
+### Authentication still required
+
+**Problem**: Getting 401 errors even with proxy headers.
+
+**Check**:
+1. Verify `MCP_CLIENT_AUTH_ENABLED=false`
+2. Ensure `TRUST_PROXY_AUTH=true`
+3. Confirm header name matches `PROXY_USER_HEADER`
+4. Check proxy is actually sending the header
+
+### WebSocket connections fail
+
+**Problem**: WebSocket connections are rejected.
+
+**Solution**: Ensure your proxy passes headers to WebSocket upgrade requests. Some proxies require special configuration for WebSocket support.
+
+## Migration from JWT Authentication
+
+To migrate from JWT to proxy authentication:
+
+1. **Deploy proxy** alongside existing setup
+2. **Test proxy** authentication with a subset of users
+3. **Update environment**:
+   ```bash
+   MCP_CLIENT_AUTH_ENABLED=false
+   TRUST_PROXY_AUTH=true
+   PROXY_USER_HEADER=X-Authenticated-User
+   ```
+4. **Monitor logs** for authentication issues
+5. **Remove JWT** token generation once stable
+
+## Security Best Practices
+
+1. **Never expose MCP Gateway directly** when proxy auth is enabled
+2. **Use TLS** between proxy and gateway
+3. **Validate proxy certificates** in production
+4. **Monitor** authentication logs for anomalies
+5. **Implement rate limiting** at the proxy level
+6. **Use network policies** to ensure only the proxy can reach the gateway
+
+## Example: Complete Setup with Traefik
+
+```yaml
+# docker-compose.yml
+version: '3.8'
+
+services:
+  traefik:
+    image: traefik:v2.10
+    command:
+      - "--providers.docker=true"
+      - "--entrypoints.web.address=:80"
+    ports:
+      - "80:80"
+    volumes:
+      - /var/run/docker.sock:/var/run/docker.sock
+
+  oauth2-proxy:
+    image: quay.io/oauth2-proxy/oauth2-proxy:latest
+    environment:
+      OAUTH2_PROXY_CLIENT_ID: ${CLIENT_ID}
+      OAUTH2_PROXY_CLIENT_SECRET: ${CLIENT_SECRET}
+      OAUTH2_PROXY_COOKIE_SECRET: ${COOKIE_SECRET}
+      OAUTH2_PROXY_PROVIDER: google
+      OAUTH2_PROXY_EMAIL_DOMAINS: "*"
+      OAUTH2_PROXY_UPSTREAMS: "http://mcp-gateway:4444/"
+      OAUTH2_PROXY_HTTP_ADDRESS: "0.0.0.0:4180"
+      OAUTH2_PROXY_PASS_USER_HEADERS: "true"
+      OAUTH2_PROXY_SET_XAUTHREQUEST: "true"
+    labels:
+      - "traefik.enable=true"
+      - "traefik.http.routers.oauth2-proxy.rule=Host(`mcp.example.com`)"
+      - "traefik.http.services.oauth2-proxy.loadbalancer.server.port=4180"
+
+  mcp-gateway:
+    image: ghcr.io/contingentai/mcp-gateway:latest
+    environment:
+      MCP_CLIENT_AUTH_ENABLED: "false"
+      TRUST_PROXY_AUTH: "true"
+      PROXY_USER_HEADER: "X-Auth-Request-Email"
+      AUTH_REQUIRED: "true"  # Keep admin UI protected
+      BASIC_AUTH_USER: ${ADMIN_USER}
+      BASIC_AUTH_PASSWORD: ${ADMIN_PASSWORD}
+    volumes:
+      - ./data:/data
+```
+
+This configuration provides Google OAuth authentication for all MCP Gateway endpoints while maintaining separate admin UI authentication.
\ No newline at end of file
diff --git a/docs/docs/manage/proxy.md b/docs/docs/manage/proxy.md
new file mode 100644
index 00000000..a110b44d
--- /dev/null
+++ b/docs/docs/manage/proxy.md
@@ -0,0 +1,668 @@
+# Proxy Authentication
+
+Configure MCP Gateway to work with authentication proxies for enterprise deployments.
+
+## Overview
+
+MCP Gateway supports proxy authentication, allowing you to disable built-in JWT authentication and rely on an upstream authentication proxy. This is essential for enterprise deployments where authentication is centralized through OAuth2, SAML, or other identity providers.
+
+## Architecture
+
+### Standard JWT Authentication Flow
+
+```mermaid
+sequenceDiagram
+    participant Client
+    participant Gateway as MCP Gateway
+    participant MCP as MCP Server
+    
+    Client->>Client: Generate JWT Token
+    Client->>Gateway: Request + Bearer Token
+    Gateway->>Gateway: Validate JWT
+    alt Valid Token
+        Gateway->>MCP: Forward Request
+        MCP-->>Gateway: Response
+        Gateway-->>Client: Response
+    else Invalid Token
+        Gateway-->>Client: 401 Unauthorized
+    end
+```
+
+### Proxy Authentication Flow
+
+```mermaid
+sequenceDiagram
+    participant User
+    participant Proxy as Auth Proxy
+    participant IDP as Identity Provider
+    participant Gateway as MCP Gateway
+    participant MCP as MCP Server
+    
+    User->>Proxy: Request
+    Proxy->>IDP: Validate Session
+    IDP-->>Proxy: User Identity
+    Proxy->>Gateway: Request + X-Authenticated-User
+    Gateway->>Gateway: Extract User from Header
+    Gateway->>MCP: Forward Request
+    MCP-->>Gateway: Response
+    Gateway-->>Proxy: Response
+    Proxy-->>User: Response
+```
+
+## Configuration
+
+### Environment Variables
+
+| Variable | Default | Description |
+|----------|---------|-------------|
+| `MCP_CLIENT_AUTH_ENABLED` | `true` | Enable/disable JWT authentication for MCP operations |
+| `TRUST_PROXY_AUTH` | `false` | Trust proxy authentication headers (required when `MCP_CLIENT_AUTH_ENABLED=false`) |
+| `PROXY_USER_HEADER` | `X-Authenticated-User` | Header containing authenticated username from proxy |
+| `AUTH_REQUIRED` | `true` | Controls admin UI authentication (independent of MCP auth) |
+
+!!! warning "Security Notice"
+    Only set `MCP_CLIENT_AUTH_ENABLED=false` when MCP Gateway is deployed behind a trusted authentication proxy. Setting `TRUST_PROXY_AUTH=true` explicitly acknowledges this security requirement.
+
+### Basic Configuration
+
+```bash title=".env"
+# Disable MCP client JWT authentication
+MCP_CLIENT_AUTH_ENABLED=false
+
+# Trust proxy authentication headers
+TRUST_PROXY_AUTH=true
+
+# Header containing authenticated user
+PROXY_USER_HEADER=X-Authenticated-User
+
+# Keep admin UI protected
+AUTH_REQUIRED=true
+BASIC_AUTH_USER=admin
+BASIC_AUTH_PASSWORD=secure-password
+```
+
+## Deployment Patterns
+
+### Pattern 1: OAuth2 Proxy
+
+```mermaid
+graph LR
+    User[User] -->|HTTPS| LB[Load Balancer]
+    LB --> OAuth[OAuth2 Proxy]
+    OAuth -->|X-Auth-Request-User| Gateway[MCP Gateway]
+    Gateway --> MCP1[MCP Server 1]
+    Gateway --> MCP2[MCP Server 2]
+    
+    OAuth -.->|OAuth Flow| IDP[Google/GitHub/etc]
+    
+    style OAuth fill:#f9f,stroke:#333,stroke-width:2px
+    style Gateway fill:#bbf,stroke:#333,stroke-width:2px
+```
+
+#### Docker Compose Example
+
+```yaml title="docker-compose.yml"
+version: '3.8'
+
+services:
+  oauth2-proxy:
+    image: quay.io/oauth2-proxy/oauth2-proxy:v7.5.0
+    command:
+      - --http-address=0.0.0.0:4180
+      - --upstream=http://mcp-gateway:4444
+      - --email-domain=*
+      - --pass-user-headers=true
+      - --set-xauthrequest=true
+      - --skip-provider-button=true
+    environment:
+      OAUTH2_PROXY_CLIENT_ID: ${OAUTH_CLIENT_ID}
+      OAUTH2_PROXY_CLIENT_SECRET: ${OAUTH_CLIENT_SECRET}
+      OAUTH2_PROXY_COOKIE_SECRET: ${COOKIE_SECRET}
+      OAUTH2_PROXY_PROVIDER: google
+    ports:
+      - "4180:4180"
+    networks:
+      - mcp-network
+
+  mcp-gateway:
+    image: ghcr.io/contingentai/mcp-gateway:latest
+    environment:
+      MCP_CLIENT_AUTH_ENABLED: "false"
+      TRUST_PROXY_AUTH: "true"
+      PROXY_USER_HEADER: "X-Auth-Request-Email"
+      AUTH_REQUIRED: "true"
+      BASIC_AUTH_USER: ${ADMIN_USER}
+      BASIC_AUTH_PASSWORD: ${ADMIN_PASSWORD}
+      DATABASE_URL: postgresql://postgres:password@db:5432/mcp
+    depends_on:
+      - db
+    networks:
+      - mcp-network
+
+  db:
+    image: postgres:15
+    environment:
+      POSTGRES_DB: mcp
+      POSTGRES_USER: postgres
+      POSTGRES_PASSWORD: password
+    volumes:
+      - postgres_data:/var/lib/postgresql/data
+    networks:
+      - mcp-network
+
+networks:
+  mcp-network:
+    driver: bridge
+
+volumes:
+  postgres_data:
+```
+
+### Pattern 2: Kubernetes with Istio
+
+```mermaid
+graph TB
+    subgraph "Kubernetes Cluster"
+        subgraph "Istio Service Mesh"
+            IG[Istio Gateway] --> VS[Virtual Service]
+            VS --> AuthZ[Authorization Policy]
+            AuthZ --> Gateway[MCP Gateway Pod]
+        end
+        
+        Gateway --> MCP1[MCP Server Pod 1]
+        Gateway --> MCP2[MCP Server Pod 2]
+        
+        OIDC[OIDC Provider] -.->|JWT Validation| AuthZ
+    end
+    
+    User[User] -->|HTTPS + JWT| IG
+    
+    style AuthZ fill:#f96,stroke:#333,stroke-width:2px
+    style Gateway fill:#bbf,stroke:#333,stroke-width:2px
+```
+
+#### Kubernetes Manifests
+
+```yaml title="mcp-gateway-deployment.yaml"
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: mcp-gateway
+  namespace: mcp-system
+spec:
+  replicas: 3
+  selector:
+    matchLabels:
+      app: mcp-gateway
+  template:
+    metadata:
+      labels:
+        app: mcp-gateway
+        version: v1
+    spec:
+      containers:
+      - name: mcp-gateway
+        image: ghcr.io/contingentai/mcp-gateway:latest
+        env:
+        - name: MCP_CLIENT_AUTH_ENABLED
+          value: "false"
+        - name: TRUST_PROXY_AUTH
+          value: "true"
+        - name: PROXY_USER_HEADER
+          value: "X-User-Id"
+        - name: DATABASE_URL
+          valueFrom:
+            secretKeyRef:
+              name: mcp-gateway-secrets
+              key: database-url
+        ports:
+        - containerPort: 4444
+          name: http
+        livenessProbe:
+          httpGet:
+            path: /health
+            port: http
+          initialDelaySeconds: 30
+          periodSeconds: 10
+        readinessProbe:
+          httpGet:
+            path: /ready
+            port: http
+          initialDelaySeconds: 5
+          periodSeconds: 5
+```
+
+```yaml title="istio-authorization.yaml"
+apiVersion: security.istio.io/v1beta1
+kind: RequestAuthentication
+metadata:
+  name: mcp-gateway-jwt
+  namespace: mcp-system
+spec:
+  selector:
+    matchLabels:
+      app: mcp-gateway
+  jwtRules:
+  - issuer: "https://accounts.google.com"
+    jwksUri: "https://www.googleapis.com/oauth2/v3/certs"
+    outputPayloadToHeader: "X-User-Id"
+---
+apiVersion: security.istio.io/v1beta1
+kind: AuthorizationPolicy
+metadata:
+  name: mcp-gateway-authz
+  namespace: mcp-system
+spec:
+  selector:
+    matchLabels:
+      app: mcp-gateway
+  action: ALLOW
+  rules:
+  - from:
+    - source:
+        requestPrincipals: ["*"]
+```
+
+### Pattern 3: API Gateway (Kong)
+
+```mermaid
+graph LR
+    subgraph "Kong API Gateway"
+        Plugin[OIDC Plugin] --> Route[Route]
+        Route --> Service[Service]
+    end
+    
+    User[User] -->|HTTPS| Plugin
+    Service -->|X-Consumer-Username| Gateway[MCP Gateway]
+    Gateway --> MCP[MCP Servers]
+    
+    Plugin -.->|OIDC Flow| IDP[Keycloak/Auth0]
+    
+    style Plugin fill:#f9f,stroke:#333,stroke-width:2px
+    style Gateway fill:#bbf,stroke:#333,stroke-width:2px
+```
+
+#### Kong Configuration
+
+```yaml title="kong-config.yaml"
+services:
+  - name: mcp-gateway
+    url: http://mcp-gateway:4444
+    routes:
+      - name: mcp-route
+        paths:
+          - /mcp
+    plugins:
+      - name: oidc
+        config:
+          client_id: mcp-client
+          client_secret: ${OIDC_SECRET}
+          discovery: https://auth.example.com/.well-known/openid-configuration
+          introspection_endpoint: https://auth.example.com/introspect
+          bearer_only: "yes"
+          realm: mcp-gateway
+          header_names:
+            - X-Consumer-Username:preferred_username
+            - X-Consumer-Id:sub
+```
+
+## Common Proxy Configurations
+
+### Authelia
+
+```yaml title="authelia-config.yml"
+authentication_backend:
+  ldap:
+    url: ldaps://ldap.example.com
+    base_dn: dc=example,dc=com
+
+access_control:
+  default_policy: deny
+  rules:
+    - domain: mcp.example.com
+      policy: two_factor
+      subject:
+        - group:mcp-users
+
+# Headers forwarded to backend
+authorization:
+  headers:
+    Remote-User: username
+    Remote-Email: email
+    Remote-Groups: groups
+```
+
+MCP Gateway configuration:
+```bash
+MCP_CLIENT_AUTH_ENABLED=false
+TRUST_PROXY_AUTH=true
+PROXY_USER_HEADER=Remote-User
+```
+
+### Cloudflare Access
+
+```mermaid
+graph LR
+    User[User] -->|HTTPS| CF[Cloudflare Edge]
+    CF -->|Cf-Access-Jwt-Assertion| Gateway[MCP Gateway]
+    
+    CF -.->|SAML/OIDC| IDP[Identity Provider]
+    
+    style CF fill:#f90,stroke:#333,stroke-width:2px
+    style Gateway fill:#bbf,stroke:#333,stroke-width:2px
+```
+
+Configuration:
+```bash
+MCP_CLIENT_AUTH_ENABLED=false
+TRUST_PROXY_AUTH=true
+PROXY_USER_HEADER=Cf-Access-Authenticated-User-Email
+```
+
+### AWS ALB with Cognito
+
+```bash
+MCP_CLIENT_AUTH_ENABLED=false
+TRUST_PROXY_AUTH=true
+PROXY_USER_HEADER=X-Amzn-Oidc-Identity
+```
+
+## Header Passthrough
+
+When using proxy authentication, you often need to pass additional headers to downstream MCP servers:
+
+```bash
+# Enable header passthrough
+ENABLE_HEADER_PASSTHROUGH=true
+
+# Headers to pass through (JSON array)
+DEFAULT_PASSTHROUGH_HEADERS='["X-Tenant-Id", "X-Request-Id", "X-Authenticated-User", "X-Groups"]'
+```
+
+## Security Considerations
+
+### Network Isolation
+
+```mermaid
+graph TB
+    subgraph "DMZ"
+        WAF[WAF] --> LB[Load Balancer]
+        LB --> Proxy[Auth Proxy]
+    end
+    
+    subgraph "Private Network"
+        Proxy -->|Internal Only| Gateway[MCP Gateway]
+        Gateway --> MCP1[MCP Server 1]
+        Gateway --> MCP2[MCP Server 2]
+    end
+    
+    Internet[Internet] -->|HTTPS| WAF
+    
+    style Proxy fill:#f96,stroke:#333,stroke-width:2px
+    style Gateway fill:#bbf,stroke:#333,stroke-width:2px
+```
+
+!!! danger "Critical Security Requirements"
+    1. **Never expose MCP Gateway directly** to the internet when proxy auth is enabled
+    2. **Use TLS** for all communication between proxy and gateway
+    3. **Implement network policies** to ensure only the proxy can reach the gateway
+    4. **Validate proxy certificates** in production environments
+    5. **Monitor authentication logs** for suspicious activity
+
+### Recommended Security Headers
+
+Configure your proxy to add these security headers:
+
+```nginx title="nginx.conf"
+# Security headers
+add_header X-Frame-Options "DENY" always;
+add_header X-Content-Type-Options "nosniff" always;
+add_header X-XSS-Protection "1; mode=block" always;
+add_header Referrer-Policy "strict-origin-when-cross-origin" always;
+add_header Content-Security-Policy "default-src 'self'" always;
+
+# Remove sensitive headers
+proxy_hide_header X-Powered-By;
+proxy_hide_header Server;
+
+# Pass authentication headers
+proxy_set_header X-Authenticated-User $remote_user;
+proxy_set_header X-Real-IP $remote_addr;
+proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+proxy_set_header X-Forwarded-Proto $scheme;
+```
+
+## Testing
+
+### Verify Configuration
+
+```bash
+# Test without authentication (should fail or return anonymous)
+curl -v http://localhost:4444/tools
+
+# Test with proxy header
+curl -H "X-Authenticated-User: john.doe@example.com" \
+     http://localhost:4444/tools
+
+# Test WebSocket with proxy header
+wscat -c ws://localhost:4444/ws \
+      -H "X-Authenticated-User: john.doe@example.com"
+```
+
+### Health Checks
+
+Configure your load balancer to use these endpoints:
+
+| Endpoint | Purpose | Expected Response |
+|----------|---------|-------------------|
+| `/health` | Liveness probe | 200 OK |
+| `/ready` | Readiness probe | 200 OK when ready |
+| `/metrics` | Prometheus metrics | Metrics in text format |
+
+## Troubleshooting
+
+### Common Issues
+
+??? question "Getting 401 Unauthorized with proxy headers"
+    **Check these settings:**
+    
+    1. Verify `MCP_CLIENT_AUTH_ENABLED=false`
+    2. Ensure `TRUST_PROXY_AUTH=true`
+    3. Confirm header name matches `PROXY_USER_HEADER`
+    4. Check proxy is sending the header:
+    ```bash
+    # Debug headers being received
+    curl -H "X-Authenticated-User: test" \
+         http://localhost:4444/version -v
+    ```
+
+??? question "Warning: MCP auth disabled without trust"
+    **You're seeing:**
+    ```
+    WARNING - MCP client authentication is disabled but trust_proxy_auth is not set
+    ```
+    
+    **Solution:** Set `TRUST_PROXY_AUTH=true` to acknowledge proxy authentication.
+
+??? question "WebSocket connections fail"
+    **Common causes:**
+    
+    1. Proxy not passing headers on WebSocket upgrade
+    2. Missing WebSocket support in proxy
+    
+    **nginx fix:**
+    ```nginx
+    location /ws {
+        proxy_pass http://mcp-gateway:4444;
+        proxy_http_version 1.1;
+        proxy_set_header Upgrade $http_upgrade;
+        proxy_set_header Connection "upgrade";
+        proxy_set_header X-Authenticated-User $remote_user;
+    }
+    ```
+
+??? question "How to handle multiple authentication methods?"
+    **Use virtual servers with different auth configs:**
+    
+    ```yaml
+    # Server 1: Proxy auth
+    - name: internal-server
+      auth_mode: proxy
+      proxy_header: X-Employee-Id
+    
+    # Server 2: JWT auth
+    - name: external-server
+      auth_mode: jwt
+      jwt_audience: external-clients
+    ```
+
+## Migration Guide
+
+### From JWT to Proxy Authentication
+
+```mermaid
+graph LR
+    subgraph "Phase 1: Preparation"
+        A1[Document Current Auth] --> A2[Deploy Proxy]
+        A2 --> A3[Test Proxy Auth]
+    end
+    
+    subgraph "Phase 2: Dual Mode"
+        B1[Enable Both Auth] --> B2[Migrate Clients]
+        B2 --> B3[Monitor Logs]
+    end
+    
+    subgraph "Phase 3: Proxy Only"
+        C1[Disable JWT Auth] --> C2[Remove JWT Code]
+        C2 --> C3[Document Change]
+    end
+    
+    A3 --> B1
+    B3 --> C1
+```
+
+#### Step-by-Step Migration
+
+=== "Step 1: Deploy Proxy"
+    ```bash
+    # Deploy auth proxy alongside existing setup
+    docker-compose up -d oauth2-proxy
+    
+    # Test proxy authentication
+    curl -H "Authorization: Bearer $TOKEN" \
+         http://localhost:4180/health
+    ```
+
+=== "Step 2: Enable Dual Mode"
+    ```bash
+    # Keep JWT auth but allow proxy headers
+    MCP_CLIENT_AUTH_ENABLED=true
+    TRUST_PROXY_AUTH=true
+    PROXY_USER_HEADER=X-Auth-Request-Email
+    ```
+
+=== "Step 3: Test Both Methods"
+    ```bash
+    # Test JWT (existing)
+    curl -H "Authorization: Bearer $JWT_TOKEN" \
+         http://localhost:4444/tools
+    
+    # Test proxy header (new)
+    curl -H "X-Auth-Request-Email: user@example.com" \
+         http://localhost:4444/tools
+    ```
+
+=== "Step 4: Switch to Proxy Only"
+    ```bash
+    # Disable JWT authentication
+    MCP_CLIENT_AUTH_ENABLED=false
+    TRUST_PROXY_AUTH=true
+    
+    # Restart gateway
+    docker-compose restart mcp-gateway
+    ```
+
+## Performance Considerations
+
+### Caching User Identity
+
+```mermaid
+graph LR
+    subgraph "With Caching"
+        Proxy1[Auth Proxy] --> Cache{Redis Cache}
+        Cache -->|Hit| Gateway1[MCP Gateway]
+        Cache -->|Miss| IDP1[IDP]
+        IDP1 --> Cache
+    end
+    
+    subgraph "Without Caching"
+        Proxy2[Auth Proxy] --> IDP2[IDP]
+        IDP2 --> Gateway2[MCP Gateway]
+    end
+    
+    style Cache fill:#9f9,stroke:#333,stroke-width:2px
+```
+
+Configure Redis caching for better performance:
+
+```bash
+# Enable Redis cache
+CACHE_TYPE=redis
+REDIS_URL=redis://localhost:6379/0
+
+# Cache user sessions
+SESSION_TTL=3600  # 1 hour
+```
+
+## Monitoring
+
+### Key Metrics
+
+| Metric | Description | Alert Threshold |
+|--------|-------------|-----------------|
+| `mcp_auth_failures_total` | Failed authentication attempts | > 10/min |
+| `mcp_proxy_header_missing` | Requests without proxy header | > 5/min |
+| `mcp_auth_latency_seconds` | Authentication processing time | > 1s p99 |
+
+### Grafana Dashboard
+
+```json title="grafana-dashboard.json"
+{
+  "dashboard": {
+    "title": "MCP Gateway - Proxy Auth",
+    "panels": [
+      {
+        "title": "Auth Success Rate",
+        "targets": [{
+          "expr": "rate(mcp_auth_success_total[5m]) / rate(mcp_auth_attempts_total[5m])"
+        }]
+      },
+      {
+        "title": "Users by Proxy Header",
+        "targets": [{
+          "expr": "count by (user) (mcp_authenticated_requests_total)"
+        }]
+      }
+    ]
+  }
+}
+```
+
+## Best Practices
+
+!!! tip "Production Checklist"
+    - [ ] Network isolation between proxy and gateway
+    - [ ] TLS encryption for all connections
+    - [ ] Rate limiting at proxy level
+    - [ ] Audit logging enabled
+    - [ ] Monitoring and alerting configured
+    - [ ] Backup authentication method available
+    - [ ] Documentation updated
+    - [ ] Security review completed
+
+## Related Documentation
+
+- [Authentication Overview](../authentication.md)
+- [Security Best Practices](../security.md)
+- [Deployment Guide](../deployment/index.md)
+- [Federation Setup](../federation.md)
\ No newline at end of file
diff --git a/mcpgateway/config.py b/mcpgateway/config.py
index da8ecd26..42a27fdb 100644
--- a/mcpgateway/config.py
+++ b/mcpgateway/config.py
@@ -124,6 +124,14 @@ class Settings(BaseSettings):
 
     require_token_expiration: bool = Field(default=False, description="Require all JWT tokens to have expiration claims")  # Default to flexible mode for backward compatibility
 
+    # MCP Client Authentication
+    mcp_client_auth_enabled: bool = Field(default=True, description="Enable JWT authentication for MCP client operations")
+    trust_proxy_auth: bool = Field(
+        default=False,
+        description="Trust proxy authentication headers (required when mcp_client_auth_enabled=false)",
+    )
+    proxy_user_header: str = Field(default="X-Authenticated-User", description="Header containing authenticated username from proxy")
+
     #  Encryption key phrase for auth storage
     auth_encryption_secret: str = "my-test-salt"
 
@@ -631,6 +639,14 @@ def __init__(self, **kwargs):
             # Safer defaults without Authorization header
             self.default_passthrough_headers = ["X-Tenant-Id", "X-Trace-Id"]
 
+        # Validate proxy auth configuration
+        if not self.mcp_client_auth_enabled and not self.trust_proxy_auth:
+            logger.warning(
+                "MCP client authentication is disabled but trust_proxy_auth is not set. "
+                "This is a security risk! Set TRUST_PROXY_AUTH=true only if MCP Gateway "
+                "is behind a trusted authentication proxy."
+            )
+
     # Masking value for all sensitive data
     masked_auth_value: str = "*****"
 
diff --git a/mcpgateway/main.py b/mcpgateway/main.py
index 2ba43f71..684c5ac3 100644
--- a/mcpgateway/main.py
+++ b/mcpgateway/main.py
@@ -97,7 +97,7 @@
 from mcpgateway.utils.error_formatter import ErrorFormatter
 from mcpgateway.utils.redis_isready import wait_for_redis_ready
 from mcpgateway.utils.retry_manager import ResilientHttpClient
-from mcpgateway.utils.verify_credentials import require_auth, require_auth_override
+from mcpgateway.utils.verify_credentials import require_auth, require_auth_override, verify_jwt_token
 from mcpgateway.validation.jsonrpc import JSONRPCError
 
 # Import the admin routes from the new module
@@ -2277,6 +2277,37 @@ async def websocket_endpoint(websocket: WebSocket):
         websocket: The WebSocket connection instance.
     """
     try:
+        # Authenticate WebSocket connection
+        if settings.mcp_client_auth_enabled or settings.auth_required:
+            # Extract auth from query params or headers
+            token = None
+            # Try to get token from query parameter
+            if "token" in websocket.query_params:
+                token = websocket.query_params["token"]
+            # Try to get token from Authorization header
+            elif "authorization" in websocket.headers:
+                auth_header = websocket.headers["authorization"]
+                if auth_header.startswith("Bearer "):
+                    token = auth_header[7:]
+
+            # Check for proxy auth if MCP client auth is disabled
+            if not settings.mcp_client_auth_enabled and settings.trust_proxy_auth:
+                proxy_user = websocket.headers.get(settings.proxy_user_header)
+                if not proxy_user and not token:
+                    await websocket.close(code=1008, reason="Authentication required")
+                    return
+            elif settings.auth_required and not token:
+                await websocket.close(code=1008, reason="Authentication required")
+                return
+
+            # Verify JWT token if provided and MCP client auth is enabled
+            if token and settings.mcp_client_auth_enabled:
+                try:
+                    await verify_jwt_token(token)
+                except Exception:
+                    await websocket.close(code=1008, reason="Invalid authentication")
+                    return
+
         await websocket.accept()
         while True:
             try:
diff --git a/mcpgateway/utils/verify_credentials.py b/mcpgateway/utils/verify_credentials.py
index a980f3f0..4944f828 100644
--- a/mcpgateway/utils/verify_credentials.py
+++ b/mcpgateway/utils/verify_credentials.py
@@ -46,7 +46,7 @@
 from typing import Optional
 
 # Third-Party
-from fastapi import Cookie, Depends, HTTPException, status
+from fastapi import Cookie, Depends, HTTPException, Request, status
 from fastapi.security import HTTPAuthorizationCredentials, HTTPBasic, HTTPBasicCredentials, HTTPBearer
 from fastapi.security.utils import get_authorization_scheme_param
 import jwt
@@ -213,20 +213,24 @@ async def verify_credentials(token: str) -> dict:
     return payload
 
 
-async def require_auth(credentials: Optional[HTTPAuthorizationCredentials] = Depends(security), jwt_token: Optional[str] = Cookie(None)) -> str | dict:
-    """Require authentication via JWT token.
+async def require_auth(request: Request, credentials: Optional[HTTPAuthorizationCredentials] = Depends(security), jwt_token: Optional[str] = Cookie(None)) -> str | dict:
+    """Require authentication via JWT token or proxy headers.
 
-    FastAPI dependency that checks for a JWT token either in the Authorization
-    header (Bearer scheme) or as a cookie. If authentication is required but
-    no token is provided, raises an HTTP 401 error.
+    FastAPI dependency that checks for authentication via:
+    1. Proxy headers (if mcp_client_auth_enabled=false and trust_proxy_auth=true)
+    2. JWT token in Authorization header (Bearer scheme)
+    3. JWT token in cookies
+
+    If authentication is required but no token is provided, raises an HTTP 401 error.
 
     Args:
+        request: The FastAPI request object for accessing headers.
         credentials: HTTP Authorization credentials from the request header.
         jwt_token: JWT token from cookies.
 
     Returns:
         str | dict: The verified credentials payload if authenticated,
-            or "anonymous" if authentication is not required.
+            proxy user if proxy auth enabled, or "anonymous" if authentication is not required.
 
     Raises:
         HTTPException: 401 status if authentication is required but no valid
@@ -240,39 +244,59 @@ async def require_auth(credentials: Optional[HTTPAuthorizationCredentials] = Dep
         ...     basic_auth_user = 'user'
         ...     basic_auth_password = 'pass'
         ...     auth_required = True
+        ...     mcp_client_auth_enabled = True
+        ...     trust_proxy_auth = False
+        ...     proxy_user_header = 'X-Authenticated-User'
         ...     require_token_expiration = False
         ...     docs_allow_basic_auth = False
         >>> vc.settings = DummySettings()
         >>> import jwt
         >>> from fastapi.security import HTTPAuthorizationCredentials
+        >>> from fastapi import Request
         >>> import asyncio
 
         Test with valid credentials in header:
         >>> token = jwt.encode({'sub': 'alice'}, 'secret', algorithm='HS256')
         >>> creds = HTTPAuthorizationCredentials(scheme='Bearer', credentials=token)
-        >>> result = asyncio.run(vc.require_auth(credentials=creds, jwt_token=None))
+        >>> req = Request(scope={'type': 'http', 'headers': []})
+        >>> result = asyncio.run(vc.require_auth(request=req, credentials=creds, jwt_token=None))
         >>> result['sub'] == 'alice'
         True
 
         Test with valid token in cookie:
-        >>> result = asyncio.run(vc.require_auth(credentials=None, jwt_token=token))
+        >>> result = asyncio.run(vc.require_auth(request=req, credentials=None, jwt_token=token))
         >>> result['sub'] == 'alice'
         True
 
         Test with auth required but no token:
         >>> try:
-        ...     asyncio.run(vc.require_auth(credentials=None, jwt_token=None))
+        ...     asyncio.run(vc.require_auth(request=req, credentials=None, jwt_token=None))
         ... except vc.HTTPException as e:
         ...     print(e.status_code, e.detail)
         401 Not authenticated
 
         Test with auth not required:
         >>> vc.settings.auth_required = False
-        >>> result = asyncio.run(vc.require_auth(credentials=None, jwt_token=None))
+        >>> result = asyncio.run(vc.require_auth(request=req, credentials=None, jwt_token=None))
         >>> result
         'anonymous'
         >>> vc.settings.auth_required = True
     """
+    # If MCP client auth is disabled and proxy auth is trusted, use proxy headers
+    if not settings.mcp_client_auth_enabled:
+        if settings.trust_proxy_auth:
+            # Extract user from proxy header
+            proxy_user = request.headers.get(settings.proxy_user_header)
+            if proxy_user:
+                return {"sub": proxy_user, "source": "proxy", "token": None}
+            # If no proxy header but proxy auth is trusted, treat as anonymous
+            return "anonymous"
+        else:
+            # Warning: MCP auth disabled without proxy trust - security risk!
+            # This case is already warned about in config validation
+            return "anonymous"
+
+    # Standard JWT authentication flow
     token = credentials.credentials if credentials else jwt_token
 
     if settings.auth_required and not token:
@@ -490,6 +514,7 @@ async def require_docs_basic_auth(auth_header: str) -> str:
 async def require_auth_override(
     auth_header: str | None = None,
     jwt_token: str | None = None,
+    request: Request | None = None,
 ) -> str | dict:
     """Call require_auth manually from middleware without FastAPI dependency injection.
 
@@ -502,6 +527,7 @@ async def require_auth_override(
         auth_header: Raw Authorization header value (e.g. "Bearer eyJhbGciOi...").
         jwt_token: JWT taken from a cookie. If both header and cookie are
             supplied, the header takes precedence.
+        request: Optional Request object for accessing headers (used for proxy auth).
 
     Returns:
         str | dict: The decoded JWT payload or the string "anonymous",
@@ -523,6 +549,9 @@ async def require_auth_override(
         ...     basic_auth_user = 'user'
         ...     basic_auth_password = 'pass'
         ...     auth_required = True
+        ...     mcp_client_auth_enabled = True
+        ...     trust_proxy_auth = False
+        ...     proxy_user_header = 'X-Authenticated-User'
         ...     require_token_expiration = False
         ...     docs_allow_basic_auth = False
         >>> vc.settings = DummySettings()
@@ -554,6 +583,10 @@ async def require_auth_override(
         'anonymous'
         >>> vc.settings.auth_required = True
     """
+    # Create a mock request if not provided (for backward compatibility)
+    if request is None:
+        request = Request(scope={"type": "http", "headers": []})
+
     credentials = None
     if auth_header:
         scheme, param = get_authorization_scheme_param(auth_header)
@@ -562,4 +595,4 @@ async def require_auth_override(
         elif scheme.lower() == "basic" and param and settings.docs_allow_basic_auth:
             # Only allow Basic Auth for docs endpoints when explicitly enabled
             return await require_docs_basic_auth(auth_header)
-    return await require_auth(credentials=credentials, jwt_token=jwt_token)
+    return await require_auth(request=request, credentials=credentials, jwt_token=jwt_token)


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 73d4cc5b..e63057cf 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -69,6 +69,7 @@ def test_settings():
         basic_auth_user="testuser",
         basic_auth_password="testpass",
         auth_required=False,
+        mcp_client_auth_enabled=False,
     )
 
 
diff --git a/tests/unit/mcpgateway/test_main.py b/tests/unit/mcpgateway/test_main.py
index 6fb929f5..283c4a76 100644
--- a/tests/unit/mcpgateway/test_main.py
+++ b/tests/unit/mcpgateway/test_main.py
@@ -1073,12 +1073,20 @@ def test_set_log_level_endpoint(self, mock_set_level, test_client, auth_headers)
 class TestRealtimeEndpoints:
     """Tests for real-time communication: WebSocket, SSE, message handling, etc."""
 
+    @patch("mcpgateway.main.settings")
     @patch("mcpgateway.main.ResilientHttpClient")  # stub network calls
-    def test_websocket_endpoint(self, mock_client, test_client):
+    def test_websocket_endpoint(self, mock_client, mock_settings, test_client):
         # Standard
         from types import SimpleNamespace
 
         """Test WebSocket connection and message handling."""
+        # Configure mock settings for auth disabled
+        mock_settings.mcp_client_auth_enabled = False
+        mock_settings.auth_required = False
+        mock_settings.federation_timeout = 30
+        mock_settings.skip_ssl_verify = False
+        mock_settings.port = 4444
+
         # ----- set up async context-manager dummy -----
         mock_instance = mock_client.return_value
         mock_instance.__aenter__.return_value = mock_instance
diff --git a/tests/unit/mcpgateway/test_main_extended.py b/tests/unit/mcpgateway/test_main_extended.py
index 79a16c36..1c5b8782 100644
--- a/tests/unit/mcpgateway/test_main_extended.py
+++ b/tests/unit/mcpgateway/test_main_extended.py
@@ -226,8 +226,16 @@ def test_json_rpc_error_paths(self, test_client, auth_headers):
         # Should have either result or error
         assert "result" in body or "error" in body
 
-    def test_websocket_error_scenarios(self):
+    @patch("mcpgateway.main.settings")
+    def test_websocket_error_scenarios(self, mock_settings):
         """Test WebSocket error scenarios."""
+        # Configure mock settings for auth disabled
+        mock_settings.mcp_client_auth_enabled = False
+        mock_settings.auth_required = False
+        mock_settings.federation_timeout = 30
+        mock_settings.skip_ssl_verify = False
+        mock_settings.port = 4444
+
         with patch("mcpgateway.main.ResilientHttpClient") as mock_client:
             from types import SimpleNamespace
 
diff --git a/tests/unit/mcpgateway/utils/test_proxy_auth.py b/tests/unit/mcpgateway/utils/test_proxy_auth.py
new file mode 100644
index 00000000..ea39142d
--- /dev/null
+++ b/tests/unit/mcpgateway/utils/test_proxy_auth.py
@@ -0,0 +1,237 @@
+# -*- coding: utf-8 -*-
+"""Unit tests for proxy authentication functionality.
+
+Tests the new MCP_CLIENT_AUTH_ENABLED and proxy authentication features.
+"""
+
+import asyncio
+import pytest
+from unittest.mock import Mock, patch, AsyncMock
+from fastapi import HTTPException, Request
+from fastapi.security import HTTPAuthorizationCredentials
+
+from mcpgateway.utils import verify_credentials as vc
+
+
+class TestProxyAuthentication:
+    """Test cases for proxy authentication functionality."""
+    
+    @pytest.fixture
+    def mock_settings(self):
+        """Create mock settings for testing."""
+        class MockSettings:
+            jwt_secret_key = 'test-secret'
+            jwt_algorithm = 'HS256'
+            basic_auth_user = 'admin'
+            basic_auth_password = 'password'
+            auth_required = True
+            mcp_client_auth_enabled = True
+            trust_proxy_auth = False
+            proxy_user_header = 'X-Authenticated-User'
+            require_token_expiration = False
+            docs_allow_basic_auth = False
+        
+        return MockSettings()
+    
+    @pytest.fixture
+    def mock_request(self):
+        """Create a mock request object."""
+        request = Mock(spec=Request)
+        request.headers = {}
+        return request
+    
+    @pytest.mark.asyncio
+    async def test_standard_jwt_auth_enabled(self, mock_settings, mock_request):
+        """Test standard JWT authentication when MCP client auth is enabled."""
+        mock_settings.mcp_client_auth_enabled = True
+        mock_settings.auth_required = True
+        
+        with patch.object(vc, 'settings', mock_settings):
+            # Test with no credentials should raise exception
+            with pytest.raises(HTTPException) as exc_info:
+                await vc.require_auth(mock_request, None, None)
+            assert exc_info.value.status_code == 401
+            assert exc_info.value.detail == "Not authenticated"
+    
+    @pytest.mark.asyncio
+    async def test_proxy_auth_disabled_without_trust(self, mock_settings, mock_request):
+        """Test that disabling MCP client auth without trust returns anonymous."""
+        mock_settings.mcp_client_auth_enabled = False
+        mock_settings.trust_proxy_auth = False
+        mock_settings.auth_required = True
+        
+        with patch.object(vc, 'settings', mock_settings):
+            # Should return anonymous and log warning (warning logged in config)
+            result = await vc.require_auth(mock_request, None, None)
+            assert result == "anonymous"
+    
+    @pytest.mark.asyncio
+    async def test_proxy_auth_with_header(self, mock_settings, mock_request):
+        """Test proxy authentication with user header."""
+        mock_settings.mcp_client_auth_enabled = False
+        mock_settings.trust_proxy_auth = True
+        mock_request.headers = {'X-Authenticated-User': 'proxy-user'}
+        
+        with patch.object(vc, 'settings', mock_settings):
+            result = await vc.require_auth(mock_request, None, None)
+            assert result == {"sub": "proxy-user", "source": "proxy", "token": None}
+    
+    @pytest.mark.asyncio
+    async def test_proxy_auth_without_header(self, mock_settings, mock_request):
+        """Test proxy authentication without user header returns anonymous."""
+        mock_settings.mcp_client_auth_enabled = False
+        mock_settings.trust_proxy_auth = True
+        mock_request.headers = {}  # No proxy header
+        
+        with patch.object(vc, 'settings', mock_settings):
+            result = await vc.require_auth(mock_request, None, None)
+            assert result == "anonymous"
+    
+    @pytest.mark.asyncio
+    async def test_custom_proxy_header(self, mock_settings, mock_request):
+        """Test proxy authentication with custom header name."""
+        mock_settings.mcp_client_auth_enabled = False
+        mock_settings.trust_proxy_auth = True
+        mock_settings.proxy_user_header = 'X-Remote-User'
+        mock_request.headers = {'X-Remote-User': 'custom-user'}
+        
+        with patch.object(vc, 'settings', mock_settings):
+            result = await vc.require_auth(mock_request, None, None)
+            assert result == {"sub": "custom-user", "source": "proxy", "token": None}
+    
+    @pytest.mark.asyncio
+    async def test_jwt_auth_with_proxy_enabled(self, mock_settings, mock_request):
+        """Test that JWT auth still works when proxy auth is configured."""
+        mock_settings.mcp_client_auth_enabled = True
+        mock_settings.trust_proxy_auth = True
+        mock_settings.auth_required = False  # Allow anonymous
+        
+        # Even with proxy trust enabled, if MCP client auth is enabled, 
+        # it should use standard JWT flow
+        with patch.object(vc, 'settings', mock_settings):
+            result = await vc.require_auth(mock_request, None, None)
+            assert result == "anonymous"  # No token provided, auth not required
+    
+    @pytest.mark.asyncio
+    async def test_backwards_compatibility(self, mock_settings, mock_request):
+        """Test that existing AUTH_REQUIRED behavior is preserved."""
+        mock_settings.mcp_client_auth_enabled = True  # Default
+        mock_settings.auth_required = False
+        
+        with patch.object(vc, 'settings', mock_settings):
+            # Should return anonymous when auth not required
+            result = await vc.require_auth(mock_request, None, None)
+            assert result == "anonymous"
+    
+    @pytest.mark.asyncio
+    async def test_mixed_auth_scenario(self, mock_settings, mock_request):
+        """Test scenario with both proxy header and JWT token."""
+        import jwt
+        
+        mock_settings.mcp_client_auth_enabled = False
+        mock_settings.trust_proxy_auth = True
+        mock_request.headers = {'X-Authenticated-User': 'proxy-user'}
+        
+        # Create a valid JWT token
+        token = jwt.encode({'sub': 'jwt-user'}, mock_settings.jwt_secret_key, algorithm='HS256')
+        creds = HTTPAuthorizationCredentials(scheme='Bearer', credentials=token)
+        
+        with patch.object(vc, 'settings', mock_settings):
+            # When MCP client auth is disabled, proxy takes precedence
+            result = await vc.require_auth(mock_request, creds, None)
+            assert result == {"sub": "proxy-user", "source": "proxy", "token": None}
+
+
+class TestWebSocketAuthentication:
+    """Test cases for WebSocket authentication."""
+    
+    @pytest.mark.asyncio
+    async def test_websocket_auth_required(self):
+        """Test that WebSocket requires authentication when enabled."""
+        from fastapi import WebSocket
+        from unittest.mock import AsyncMock
+        
+        # Create mock WebSocket
+        websocket = AsyncMock(spec=WebSocket)
+        websocket.query_params = {}
+        websocket.headers = {}
+        websocket.close = AsyncMock()
+        
+        # Mock settings with auth required
+        with patch('mcpgateway.main.settings') as mock_settings:
+            mock_settings.mcp_client_auth_enabled = True
+            mock_settings.auth_required = True
+            mock_settings.trust_proxy_auth = False
+            
+            # Import and call the websocket_endpoint function
+            from mcpgateway.main import websocket_endpoint
+            
+            # Should close connection due to missing auth
+            await websocket_endpoint(websocket)
+            websocket.close.assert_called_once_with(code=1008, reason="Authentication required")
+    
+    @pytest.mark.asyncio
+    async def test_websocket_with_token_query_param(self):
+        """Test WebSocket authentication with token in query parameters."""
+        from fastapi import WebSocket
+        from unittest.mock import AsyncMock
+        import jwt
+        
+        # Create mock WebSocket
+        websocket = AsyncMock(spec=WebSocket)
+        token = jwt.encode({'sub': 'test-user'}, 'test-secret', algorithm='HS256')
+        websocket.query_params = {"token": token}
+        websocket.headers = {}
+        websocket.accept = AsyncMock()
+        websocket.receive_text = AsyncMock(side_effect=Exception("Test complete"))
+        
+        # Mock settings
+        with patch('mcpgateway.main.settings') as mock_settings:
+            mock_settings.mcp_client_auth_enabled = True
+            mock_settings.auth_required = True
+            mock_settings.port = 8000
+            
+            # Mock verify_jwt_token to succeed
+            with patch('mcpgateway.main.verify_jwt_token', new=AsyncMock(return_value={'sub': 'test-user'})):
+                from mcpgateway.main import websocket_endpoint
+                
+                try:
+                    await websocket_endpoint(websocket)
+                except Exception as e:
+                    if str(e) != "Test complete":
+                        raise
+                
+                # Should accept connection
+                websocket.accept.assert_called_once()
+    
+    @pytest.mark.asyncio
+    async def test_websocket_with_proxy_auth(self):
+        """Test WebSocket authentication with proxy headers."""
+        from fastapi import WebSocket
+        from unittest.mock import AsyncMock
+        
+        # Create mock WebSocket
+        websocket = AsyncMock(spec=WebSocket)
+        websocket.query_params = {}
+        websocket.headers = {'X-Authenticated-User': 'proxy-user'}
+        websocket.accept = AsyncMock()
+        websocket.receive_text = AsyncMock(side_effect=Exception("Test complete"))
+        
+        # Mock settings for proxy auth
+        with patch('mcpgateway.main.settings') as mock_settings:
+            mock_settings.mcp_client_auth_enabled = False
+            mock_settings.trust_proxy_auth = True
+            mock_settings.proxy_user_header = 'X-Authenticated-User'
+            mock_settings.auth_required = False
+            mock_settings.port = 8000
+            
+            from mcpgateway.main import websocket_endpoint
+            
+            try:
+                await websocket_endpoint(websocket)
+            except Exception as e:
+                if str(e) != "Test complete":
+                    raise
+            
+            # Should accept connection with proxy auth
+            websocket.accept.assert_called_once()
\ No newline at end of file
diff --git a/tests/unit/mcpgateway/utils/test_verify_credentials.py b/tests/unit/mcpgateway/utils/test_verify_credentials.py
index 79c05e3a..106ba3f6 100644
--- a/tests/unit/mcpgateway/utils/test_verify_credentials.py
+++ b/tests/unit/mcpgateway/utils/test_verify_credentials.py
@@ -27,11 +27,12 @@
 from datetime import datetime, timedelta, timezone
 
 # Third-Party
-from fastapi import HTTPException, status
+from fastapi import HTTPException, Request, status
 from fastapi.security import HTTPAuthorizationCredentials, HTTPBasicCredentials
 from fastapi.testclient import TestClient
 import jwt
 import pytest
+from unittest.mock import Mock
 
 # First-Party
 from mcpgateway.utils import verify_credentials as vc  # module under test
@@ -129,17 +130,21 @@ async def test_require_auth_header(monkeypatch):
 
     tok = _token({"uid": 7})
     creds = HTTPAuthorizationCredentials(scheme="Bearer", credentials=tok)
+    mock_request = Mock(spec=Request)
+    mock_request.headers = {}
 
-    payload = await vc.require_auth(credentials=creds, jwt_token=None)
+    payload = await vc.require_auth(request=mock_request, credentials=creds, jwt_token=None)
     assert payload["uid"] == 7
 
 
 @pytest.mark.asyncio
 async def test_require_auth_missing_token(monkeypatch):
     monkeypatch.setattr(vc.settings, "auth_required", True, raising=False)
+    mock_request = Mock(spec=Request)
+    mock_request.headers = {}
 
     with pytest.raises(HTTPException) as exc:
-        await vc.require_auth(credentials=None, jwt_token=None)
+        await vc.require_auth(request=mock_request, credentials=None, jwt_token=None)
 
     assert exc.value.status_code == status.HTTP_401_UNAUTHORIZED
     assert exc.value.detail == "Not authenticated"
@@ -214,12 +219,14 @@ async def test_require_auth_override_non_bearer(monkeypatch):
     # Arrange
     header = "Basic Zm9vOmJhcg=="  # non-Bearer scheme
     monkeypatch.setattr(vc.settings, "auth_required", False, raising=False)
+    mock_request = Mock(spec=Request)
+    mock_request.headers = {}
 
     # Act
     result = await vc.require_auth_override(auth_header=header)
 
     # Assert
-    assert result == await vc.require_auth(credentials=None, jwt_token=None)
+    assert result == await vc.require_auth(request=mock_request, credentials=None, jwt_token=None)
 
 
 @pytest.mark.asyncio