# JWT Token Vending Service for MCP Gateway

The JWT Token Vending Service provides a user-friendly mechanism for generating personal access tokens _without the use of an external IdP_ that can be used for programmatic access to MCP servers. This service bridges the gap between human authentication (web UI sessions) and machine authentication (JWT tokens), enabling users to create tokens with scoped permissions for automation, scripting, and agent access.

## The Challenge with Token Management in Enterprise MCP Deployments

In enterprise scenarios, users often need to provide programmatic access to MCP servers for various automation tasks, CI/CD pipelines, and AI agents. Traditional approaches present several challenges:

- **Manual Token Management**: Requiring users to manually generate M2M credentials through Amazon Cognito or other IdPs creates friction and security risks
- **Scope Complexity**: Users need to understand complex scope configurations and may accidentally grant excessive permissions
- **Token Lifecycle**: No centralized way to manage token expiration, renewal, or revocation
- **Audit Trail**: Difficulty tracking which tokens were generated by whom and for what purpose

## A Solution with Integrated Token Vending

The JWT Token Vending Service integrates directly with the existing MCP Gateway authentication infrastructure, allowing users to generate scoped JWT tokens through a familiar web interface.

Here is an architecture diagram showing how the token vending service integrates with the existing system:

```mermaid
graph TB
    %% Users and Token Generation Flow
    subgraph UserFlow["User Token Generation Flow"]
        direction TB
        User[User<br/>Web UI Session]
        TokenUI[Token Generation<br/>Web Interface]
        User -->|Authenticated Session| TokenUI
    end
    
    %% Core Infrastructure
    subgraph Infrastructure["MCP Gateway & Registry Infrastructure"]
        direction TB
        Nginx["Nginx<br/>Reverse Proxy"]
        AuthServer["Auth Server<br/>(Enhanced with Token Vending)"]
        Registry["Registry<br/>Web UI + Token Generation"]
        RegistryMCP["Registry<br/>MCP Server"]
    end
    
    %% Generated Token Usage
    subgraph TokenUsage["Token Usage"]
        direction TB
        Agent[AI Agent<br/>with Generated Token]
        Script[Automation Script<br/>with Generated Token]
        Pipeline[CI/CD Pipeline<br/>with Generated Token]
    end
    
    %% Identity Provider
    IdP[Identity Provider<br/>Amazon Cognito]
    
    %% MCP Server Farm
    subgraph MCPFarm["MCP Server Farm"]
        direction TB
        MCP1[MCP Server 1<br/>CurrentTime]
        MCP2[MCP Server 2<br/>FinInfo]
        MCP3[MCP Server 3<br/>Custom]
        MCPn[MCP Server n<br/>...]
    end
    
    %% Token Generation Flow
    TokenUI -->|POST /api/tokens/generate<br/>with user context| Registry
    Registry -->|POST /internal/tokens<br/>with user scopes| AuthServer
    AuthServer -->|Self-signed JWT<br/>with HMAC-SHA256| Registry
    Registry -->|Display token<br/>to user| TokenUI
    
    %% Token Usage Flow
    Agent -->|MCP requests<br/>with Bearer token| Nginx
    Script -->|API calls<br/>with Bearer token| Nginx
    Pipeline -->|Automated access<br/>with Bearer token| Nginx
    
    %% Internal routing and validation
    Nginx -->|Route /mcpgw/*<br/>Auth validation| AuthServer
    Nginx -->|Route /mcpgw/*<br/>Tool discovery| RegistryMCP
    Nginx -->|Route /tokens<br/>Token UI| Registry
    Nginx -->|Route /server1/*<br/>Proxy to MCP servers| MCP1
    Nginx -->|Route /server2/*<br/>Proxy to MCP servers| MCP2
    Nginx -->|Route /serverN/*<br/>Proxy to MCP servers| MCP3
    Nginx -->|Route /serverN/*<br/>Proxy to MCP servers| MCPn
    
    %% Auth flows
    IdP -.->|User session validation<br/>Group/scope mapping| AuthServer
    AuthServer -.->|Self-signed JWT validation<br/>Scope enforcement| AuthServer
    
    %% Styling
    classDef userStyle fill:#e8f5e8,stroke:#2e7d32,stroke-width:2px
    classDef tokenStyle fill:#fff3e0,stroke:#e65100,stroke-width:2px
    classDef agentStyle fill:#e1f5fe,stroke:#01579b,stroke-width:2px
    classDef idpStyle fill:#fff3e0,stroke:#e65100,stroke-width:2px
    classDef nginxStyle fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
    classDef authStyle fill:#ffebee,stroke:#c62828,stroke-width:2px
    classDef registryStyle fill:#fff8e1,stroke:#f57f17,stroke-width:2px
    classDef mcpStyle fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
    
    class UserFlow userStyle
    class User userStyle
    class TokenUI tokenStyle
    class TokenUsage agentStyle
    class Agent,Script,Pipeline agentStyle
    class IdP idpStyle
    class Nginx nginxStyle
    class AuthServer authStyle
    class Registry,RegistryMCP registryStyle
    class MCP1,MCP2,MCP3,MCPn mcpStyle
```

### Architecture Components for Token Vending

The JWT Token Vending Service extends the existing MCP Gateway infrastructure with new capabilities:

#### Enhanced Registry Web UI
- **Token Generation Interface**: User-friendly form for creating JWT tokens with custom scopes and expiration
- **Scope Validation**: Real-time validation ensuring requested scopes are subset of user's current permissions
- **Token Display**: Secure, one-time display of generated tokens with copy functionality and usage instructions

#### Enhanced Auth Server
- **Internal Token Endpoint**: New `/internal/tokens` endpoint for generating self-signed JWT tokens
- **Scope Validation Logic**: Ensures generated tokens cannot exceed user's current permissions
- **Rate Limiting**: Prevents token generation abuse with configurable limits per user
- **Self-Signed JWT Support**: Validates both Cognito tokens and internally generated tokens

#### Token Security Features
- **HMAC-SHA256 Signing**: Uses shared secret key for token signing and validation
- **Scope Inheritance**: Generated tokens can have same or fewer permissions than user's current scopes
- **Configurable Expiration**: Token lifetime from 1-24 hours with 8-hour default
- **Unique Token IDs**: Each token has a unique identifier for potential tracking and revocation

At a high-level the token generation and usage flow works as follows:

```mermaid
sequenceDiagram
    participant User
    participant Browser
    participant Registry as Registry<br/>Web UI
    participant AuthServer as Auth Server
    participant Agent
    participant Gateway as Gateway<br/>(Nginx)
    participant MCP as MCP Server

    %% Token Generation Flow
    Note over User,AuthServer: Token Generation Flow
    User->>Browser: Navigate to /tokens
    Browser->>Registry: GET /tokens with session cookie
    Registry->>Registry: Validate user session + extract scopes
    Registry->>Browser: Token generation form

    User->>Browser: Configure token (scopes, expiration, description)
    Browser->>Registry: POST /api/tokens/generate
    Registry->>Registry: Validate requested scopes ⊆ user scopes
    Registry->>AuthServer: POST /internal/tokens with user context
    
    AuthServer->>AuthServer: Rate limit check (10/hour/user)
    AuthServer->>AuthServer: Generate JWT with HMAC-SHA256
    AuthServer->>Registry: Return signed JWT token
    Registry->>Browser: Display token with copy functionality
    Browser->>User: Show token + usage instructions

    %% Token Usage Flow
    Note over Agent,MCP: Token Usage Flow
    User->>Agent: Provide generated JWT token
    Agent->>Gateway: MCP request with Bearer token
    Gateway->>AuthServer: Validate token + extract scopes
    
    alt Self-Signed Token
        AuthServer->>AuthServer: Detect issuer: "mcp-auth-server"
        AuthServer->>AuthServer: Validate HMAC-SHA256 signature
        AuthServer->>AuthServer: Extract scopes + enforce access
    else Cognito Token (Fallback)
        AuthServer->>AuthServer: Standard Cognito validation
    end
    
    alt Sufficient Permissions
        AuthServer->>Gateway: 200 OK + allowed scopes
        Gateway->>MCP: Forward MCP request
        MCP->>Gateway: MCP response
        Gateway->>Agent: MCP response
    else Insufficient Permissions
        AuthServer->>Gateway: 403 Access Denied
        Gateway->>Agent: 403 Access Denied
    end
```

1. A **User** authenticates to the Registry web UI using their existing session (derived from Cognito OAuth or M2M flow) which contains their current scopes and permissions.

2. The **User** navigates to the token generation interface at `/tokens` and configures their desired token parameters including optional custom scopes (must be subset of current scopes), expiration time (1-24 hours), and description.

3. The **Registry** validates the user's session, ensures requested scopes are a subset of the user's current permissions, and calls the Auth Server's internal token generation endpoint with the user context and token parameters.

4. The **Auth Server** performs security checks (rate limiting, scope validation, expiration limits) and generates a self-signed JWT token using HMAC-SHA256 with the shared secret key. The token contains standard JWT claims plus MCP-specific metadata.

5. The **Registry** displays the generated token to the user with copy functionality, usage instructions, and security warnings. The token is shown only once for security.

6. The **User** saves the token securely and provides it to their **Agent** or automation script for programmatic access to MCP servers.

7. When the **Agent** makes MCP requests, it includes the generated JWT token in the Authorization header. The **Gateway** forwards the request to the **Auth Server** for validation.

8. The **Auth Server** detects self-signed tokens by the issuer claim, validates the HMAC-SHA256 signature using the shared secret, and enforces scope-based access control using the same logic as Cognito tokens.

9. If the token is valid and the requested operation is within the token's scope, the **Gateway** forwards the request to the appropriate **MCP Server**. Otherwise, it returns a 403 Access Denied response.

The above implementation provides a seamless way for users to generate programmatic access tokens without requiring direct interaction with the Identity Provider, while maintaining the same security guarantees and scope enforcement as the existing authentication system.

## Agent Integration

The JWT Token Vending Service integrates seamlessly with existing agent authentication patterns through enhanced command-line support:

### Enhanced Agent Command Line Interface

The agent now supports direct JWT token usage through a new `--jwt-token` parameter:

```bash
# Method 1: Direct token usage
python agent.py \
  --jwt-token "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  --message "What is the current time in New York?"

# Method 2: Environment variable
export JWT_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
python agent.py --jwt-token "$JWT_TOKEN" --message "List available servers"

# Method 3: Token from file
echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." > ~/.mcp/jwt_token
python agent.py --jwt-token "$(cat ~/.mcp/jwt_token)" --message "Help me analyze data"
```

### Token Storage Best Practices

#### Secure Storage Options
```bash
# Option 1: Encrypted environment file
echo "JWT_TOKEN=your_token_here" | gpg --encrypt > ~/.mcp/token.gpg

# Option 2: System keyring (macOS)
security add-generic-password -a "$USER" -s "mcp-jwt-token" -w "your_token_here"

# Option 3: Secure file with restricted permissions
echo "your_token_here" > ~/.mcp/jwt_token
chmod 600 ~/.mcp/jwt_token
```

#### CI/CD Integration
```yaml
# GitHub Actions example
name: MCP Agent Workflow
on: [push]
jobs:
  run-agent:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Run MCP Agent
        env:
          JWT_TOKEN: ${{ secrets.MCP_JWT_TOKEN }}
        run: |
          python agent.py --jwt-token "$JWT_TOKEN" --message "Deploy analysis"
```

## Security Features and Considerations

### Token Security Implementation

#### Rate Limiting
- **Per-User Limits**: Maximum 10 tokens per user per hour
- **Sliding Window**: Uses hourly time slots for rate calculation
- **Memory-Based**: Simple in-memory counter with automatic cleanup
- **Configurable**: Limits adjustable via environment variables

#### Scope Inheritance and Validation
- **Subset Validation**: Generated tokens cannot exceed user's current permissions
- **Real-Time Validation**: Scope checks performed at generation time
- **Default Behavior**: Empty scope request defaults to user's full scope set
- **JSON Validation**: Custom scope uploads validated for proper JSON format

#### Token Lifecycle Management
- **Configurable Expiration**: 1-24 hour range with 8-hour default
- **No Refresh**: Tokens cannot be refreshed, must be regenerated
- **Unique Identifiers**: Each token has a unique `jti` claim for tracking
- **Self-Contained**: All authorization data embedded in token

### Cryptographic Implementation

#### HMAC-SHA256 Signing
```python
# Token generation process
payload = {
    "iss": "mcp-auth-server",
    "aud": "mcp-registry", 
    "sub": username,
    "scope": " ".join(requested_scopes),
    "exp": current_time + (expires_in_hours * 3600),
    "iat": current_time,
    "jti": str(uuid.uuid4()),
    "token_use": "access",
    "client_id": "user-generated",
    "token_type": "user_generated"
}

# Sign with shared secret
access_token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
```

#### Token Validation Process
```python
# Validation with issuer detection
try:
    # Quick check for self-signed tokens
    unverified_claims = jwt.decode(token, options={"verify_signature": False})
    if unverified_claims.get('iss') == 'mcp-auth-server':
        # Validate self-signed token
        claims = jwt.decode(token, SECRET_KEY, algorithms=['HS256'], 
                          issuer='mcp-auth-server', audience='mcp-registry')
        scopes = claims.get('scope', '').split()
        return {'valid': True, 'scopes': scopes, 'method': 'self_signed'}
    else:
        # Fall back to Cognito validation
        return validate_cognito_token(token)
except jwt.InvalidTokenError:
    return {'valid': False, 'error': 'Invalid token'}
```

### Security Best Practices

#### For Users
1. **Minimal Scopes**: Generate tokens with only required permissions
2. **Short Expiration**: Use shortest practical token lifetime
3. **Secure Storage**: Store tokens in encrypted or protected locations
4. **Regular Rotation**: Regenerate tokens periodically
5. **Monitor Usage**: Track token usage in application logs

#### For Administrators
1. **Audit Logging**: Monitor token generation patterns and frequency
2. **Scope Configuration**: Regularly review and update scope definitions
3. **Rate Limit Tuning**: Adjust rate limits based on usage patterns
4. **Key Management**: Protect the shared SECRET_KEY used for signing
5. **Access Reviews**: Periodically review user permissions and group memberships

### Threat Model and Mitigations

#### Token Theft
- **Risk**: Stolen tokens provide unauthorized access
- **Mitigation**: Short expiration times, scope limitations, audit logging

#### Scope Escalation
- **Risk**: Users attempt to generate tokens with excessive permissions
- **Mitigation**: Strict subset validation, real-time scope checking

#### Rate Limit Bypass
- **Risk**: Automated token generation for abuse
- **Mitigation**: Per-user rate limiting, monitoring, account lockout policies

#### Replay Attacks
- **Risk**: Intercepted tokens used maliciously
- **Mitigation**: HTTPS enforcement, short token lifetimes, unique token IDs

## Resource-Bound Tokens

By default, a generated JWT is **user-kind**: it carries the user's full OAuth scope set and can reach any server, agent, or skill the scope allows. For long-lived tokens held by agents or CI pipelines, that's often broader trust than the workflow actually needs. Resource-bound tokens narrow the trust to a single resource.

### Two token kinds

| Kind | JWT `token_kind` claim | Scope |
|------|------------------------|-------|
| User | `"user"` | Everything the user's scopes permit (today's behavior). |
| Resource-bound | `"resource"` plus `resource_type` + `resource_id` claims | One specific `(type, id)` pair only. Mismatched requests are rejected with 403 at the nginx edge. |

### Requesting a resource-bound token

Include an optional `resource` object in the `/api/tokens/generate` body:

```json
{
  "description": "CI token for the code-reviewer agent",
  "expires_in_hours": 8,
  "resource": {
    "type": "agent",
    "id": "code-reviewer"
  }
}
```

- `type` must be one of `server`, `virtual_server`, `agent`, `skill`.
- `id` is the resource path or slug. Leading and trailing slashes are normalized; `..`, percent-encoding, and control characters are rejected.
- The registry refuses to mint a binding for a resource the user cannot currently access — a bound token can never grant more than the user already had.

In the web UI, each resource detail modal (server / virtual server / agent / skill) has a **Get bound token** button that fills in the `resource` field automatically. The sidebar "Get JWT Token" and the standalone Token Generation page continue to produce user-kind tokens.

### Enforcement at the edge

The nginx `auth_request /validate` subrequest reads `token_kind` from the JWT claims:

- **User tokens** behave as they do today.
- **Resource tokens** are checked against the request URL:
  1. If `X-Original-URL` is missing, the request is rejected (fail-closed).
  2. The request path is matched against a short allow-list (`/api/auth/me` introspection) — allowed.
  3. The request path is matched against a deny-list of escalation endpoints (`/api/tokens/*`, `/api/admin/*`, `/api/search/*`, `/api/federation/*`, `/api/config/*`, `/api/peers/*`, `/api/registry/*`, other `/api/auth/*`) — rejected.
  4. Otherwise, the URL is classified into `(resource_type, resource_id)` and must match the token's claims byte-for-byte after normalization.
- Anything else: 403 with a generic error body (the specific binding is logged server-side but never sent to the client, so a leaked token cannot disclose which resource it was minted for).

### `token_kind` is required on self-signed tokens

Every JWT the gateway mints carries a `token_kind` claim (`"user"` or `"resource"`). A self-signed token **without** the claim is rejected by the edge with 403 — it is either an old artifact whose holder needs to re-mint, or a forgery attempt. External IdP tokens (Cognito, Keycloak, Entra, Okta, Auth0) and session cookies never carry `token_kind` by design and continue to flow through the unrestricted user-token path; only self-signed tokens are gated.

### Operator notes

- **Deploy ordering**: deploy the auth-server first (or simultaneously with the registry). If a new registry mints resource-bound tokens while an old auth-server is still serving `/validate`, the old instance will accept the `token_kind: "resource"` JWT as an ordinary user token — the binding is ignored until the auth-server upgrades. Prefer atomic replacement (single-task / blue-green).
- **Token re-issue required after upgrade**: any self-signed token minted before the upgrade will be rejected by the new edge guard. Users should re-mint their tokens after the upgrade. Programmatic consumers need to either regenerate or fall back to re-authenticating through their IdP.

## Implementation Configuration

### Environment Variables
```bash
# Token generation settings
MAX_TOKEN_LIFETIME_HOURS=24          # Maximum token lifetime
DEFAULT_TOKEN_LIFETIME_HOURS=8       # Default token lifetime
MAX_TOKENS_PER_USER_PER_HOUR=10     # Rate limiting

# JWT settings
JWT_ISSUER="mcp-auth-server"         # Token issuer
JWT_AUDIENCE="mcp-registry"          # Token audience
SECRET_KEY="your-shared-secret"      # HMAC signing key (must be shared)
```

### Scope Configuration
Generated tokens inherit scope validation from the existing `scopes.yml` configuration:

```yaml
# Example scope allowing read access to time servers
mcp-servers-time/read:
  - server: "currenttime"
    methods: ["initialize", "tools/list", "tools/call"]
    tools: ["current_time_by_timezone", "current_time_utc"]

# Example scope for financial data access  
mcp-servers-finance/read:
  - server: "fininfo"
    methods: ["initialize", "tools/list", "tools/call"]
    tools: ["get_stock_price", "get_market_data"]

# Admin scope with full access
mcp-registry-admin:
  - server: "*"
    methods: ["*"]
    tools: ["*"]
```

### User Interface Configuration

The token generation interface provides:

#### Token Configuration Options
- **Description**: Optional human-readable token description
- **Expiration**: Dropdown with 1, 8, and 24-hour options
- **Scope Method**: Radio buttons for "Use current scopes" or "Custom JSON"
- **Custom Scopes**: JSON textarea for advanced users

#### User Experience Features
- **Current Permissions Display**: Shows user's active scopes as badges
- **Real-time Validation**: Client-side validation of JSON scope format
- **Copy Functionality**: Multiple copy methods with fallbacks for different browsers
- **Usage Instructions**: Clear examples of how to use the generated token
- **Security Warnings**: Prominent warnings about token storage and sharing

By implementing the JWT Token Vending Service, organizations can provide their users with a secure, user-friendly way to generate programmatic access tokens while maintaining security controls and comprehensive audit capabilities. The service seamlessly integrates with existing MCP Gateway infrastructure and provides a foundation for advanced token management features.

## Integration with Token Refresh Service

The JWT Token Vending Service works seamlessly with the [Automated Token Refresh Service](token-refresh-service.md) to provide comprehensive token lifecycle management:

### Automatic Token Monitoring

Once tokens are generated through the vending service, the token refresh service automatically:

- **Monitors expiration times** for all generated tokens
- **Proactively refreshes** tokens before they expire (configurable buffer time)
- **Updates MCP client configurations** with fresh tokens
- **Maintains continuous authentication** without user intervention

### MCP Client Configuration

The token refresh service automatically generates MCP client configurations that include tokens from the vending service:

- **VS Code Extensions** - Automatically configured with refreshed tokens
- **Claude Code/Roocode** - Real-time token updates for coding assistants
- **Custom MCP Clients** - Standard configuration format for any MCP client

### Enhanced Security Model

The combination of both services provides:

- **Short-lived primary tokens** from the vending service (1-24 hours)
- **Automatic refresh capability** using secure refresh tokens
- **Zero-downtime token rotation** for continuous service availability
- **Centralized token lifecycle management** with comprehensive audit trails

### Usage Pattern

1. **Generate Initial Token** - Use the JWT Token Vending Service web interface
2. **Automatic Refresh** - Token refresh service monitors and refreshes tokens
3. **Client Integration** - MCP clients automatically use refreshed tokens
4. **Continuous Operation** - No manual intervention required for token management

For detailed setup and configuration of the token refresh service, see the [Token Refresh Service Documentation](token-refresh-service.md). 