> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentfront.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Production Checklist

> Security requirements and best practices for production deployment

Before deploying FrontMCP authentication to production, review this comprehensive checklist covering security, configuration, and operational requirements.

## Pre-Deployment Checklist

<Steps>
  <Step title="Configure Persistent Token Storage">
    Replace in-memory storage with Redis or another persistent store.

    ```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    auth: {
      mode: 'local',
      tokenStorage: {
        redis: {
          host: process.env.REDIS_HOST!,
          port: parseInt(process.env.REDIS_PORT || '6379'),
          password: process.env.REDIS_PASSWORD,
          tls: true,
        },
      },
    }
    ```
  </Step>

  <Step title="Provide Persistent Signing Keys">
    Auto-generated keys are lost on restart. Provide stable keys.

    ```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    auth: {
      local: {
        signKey: JSON.parse(process.env.JWT_SIGNING_KEY),
      },
    }
    ```
  </Step>

  <Step title="Enable Refresh Token Flow">
    For `local` and `remote` modes, opt into automatic token refresh.

    ```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    auth: {
      refresh: {
        enabled: true,
        skewSeconds: 60, // refresh tokens this many seconds before expiry
      },
    }
    ```

    In **remote** mode, token TTLs are controlled by the upstream identity provider — FrontMCP does not mint its own access/refresh tokens. In **local** mode, FrontMCP acts as the authorization server and mints its own tokens (signed with `local.signKey`).
  </Step>

  <Step title="Enable HTTPS">
    All production traffic must use TLS. Configure your reverse proxy or load balancer.
  </Step>

  <Step title="Set Expected Audience">
    For transparent mode, always validate the audience claim.

    ```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    auth: {
      mode: 'transparent',
      expectedAudience: 'https://api.yourservice.com',
    }
    ```
  </Step>
</Steps>

***

## Security Requirements

### Required

<Check>**HTTPS only** - Never expose auth endpoints over HTTP in production</Check>
<Check>**Persistent token storage** - Redis with TLS for multi-instance deployments</Check>
<Check>**Stable signing keys** - Provide keys via environment or secrets manager</Check>
<Check>**PKCE enforcement** - OAuth 2.1 requires PKCE (S256 only)</Check>
<Check>**Refresh token rotation** - Always rotate refresh tokens on use</Check>
<Check>**Audience validation** - Validate `aud` claim in transparent mode</Check>

### Recommended

<Check>**Machine ID** - Set `MACHINE_ID` env var for multi-instance session portability</Check>
<Check>**Rate limiting** - Protect token endpoints from brute force</Check>
<Check>**Monitoring** - Alert on auth failures, unusual patterns</Check>
<Check>**Key rotation** - Rotate signing keys periodically (30-90 days)</Check>
<Check>**Scope minimization** - Request only necessary scopes</Check>
<Check>**Consent UI** - Let users see what they're authorizing</Check>

***

## Configuration Reference

### Minimum Production Configuration

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import { FrontMcp } from '@frontmcp/sdk';

@FrontMcp({
  info: { name: 'ProductionServer', version: '1.0.0' },
  auth: {
    mode: 'local',
    // Persistent storage
    tokenStorage: {
      redis: {
        host: process.env.REDIS_HOST!,
        port: parseInt(process.env.REDIS_PORT || '6379'),
        password: process.env.REDIS_PASSWORD,
        tls: true,
        keyPrefix: 'auth:',
      },
    },

    // Stable keys
    local: {
      signKey: JSON.parse(process.env.JWT_SIGNING_KEY!),
    },

    // Token refresh settings
    refresh: {
      enabled: true,
      skewSeconds: 60,
    },

    // Security
    consent: { enabled: true },
  },
})
export class Server {}
```

### Environment Variables

```bash theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
# Required for Redis
REDIS_HOST=redis.example.com
REDIS_PORT=6379
REDIS_PASSWORD=your-redis-password

# Required for token signing
JWT_SIGNING_KEY='{"kty":"RSA","kid":"prod-key-1",...}'

# For remote/transparent mode
IDP_PROVIDER_URL=https://auth.example.com
IDP_CLIENT_ID=your-client-id
IDP_CLIENT_SECRET=your-client-secret
IDP_EXPECTED_AUDIENCE=https://api.yourservice.com
```

***

## Token Lifetime Guidelines

| Environment   | Access Token | Refresh Token | Rationale                |
| ------------- | ------------ | ------------- | ------------------------ |
| Development   | 1 hour       | 7 days        | Developer convenience    |
| Staging       | 30 min       | 7 days        | Balance testing/security |
| Production    | 15 min       | 7-30 days     | Security with usability  |
| High Security | 5 min        | 1 day         | Minimize exposure window |

<Tip>
  Shorter access tokens improve security but increase refresh frequency. Monitor your refresh token endpoint load.
</Tip>

***

## Key Management

### Generating Production Keys

```bash theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
# Generate RS256 key pair
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem

# Convert to JWK format (use a tool like step-cli)
step crypto jwk create --kty RSA --use sig --alg RS256 \
  public.jwk private.jwk
```

### Key Rotation Strategy

```mermaid theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
graph LR
    A[Key 1 Active] --> B[Generate Key 2]
    B --> C[Add Key 2 to JWKS]
    C --> D[Switch signing to Key 2]
    D --> E[Wait for Key 1 tokens to expire]
    E --> F[Remove Key 1 from JWKS]
```

### Configuration for Key Rotation

The `local` block accepts a single `signKey` (and optional `jwks` containing previous keys for validation during rollover). Rotation is performed by deploying a new server build with an updated `signKey` while keeping the old key in `jwks` until in-flight tokens expire.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
auth: {
  local: {
    // Private key used to sign new tokens
    signKey: currentPrivateJwk,
    // Public JWKS — clients/relying parties use this to validate tokens.
    // Include the previous public key during rollover so old (still-valid)
    // tokens continue to verify until they expire. NEVER put private key
    // material in jwks.
    jwks: { keys: [currentPublicJwk, previousPublicJwk] },
    issuer: 'https://your-server.example.com',
  },
}
```

***

## Redis Configuration

### Secure Redis Connection

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
tokenStorage: {
  redis: {
    host: 'redis.example.com',
    port: 6379,
    password: process.env.REDIS_PASSWORD,
    tls: true,
    keyPrefix: 'frontmcp:auth:',
  },
}
```

### Redis Data Stored

| Key Pattern               | Data                  | TTL          |
| ------------------------- | --------------------- | ------------ |
| `{prefix}pending:{id}`    | Pending authorization | 10 min       |
| `{prefix}code:{code}`     | Authorization code    | 60 sec       |
| `{prefix}refresh:{token}` | Refresh token         | 7-30 days    |
| `{prefix}session:{id}`    | Session data          | Configurable |
| `{prefix}vault:{session}` | Token vault           | Session TTL  |

***

## Monitoring & Alerting

### Key Metrics to Monitor

| Metric                  | Alert Threshold | Description             |
| ----------------------- | --------------- | ----------------------- |
| Auth failures/min       | > 100           | Possible brute force    |
| Token refresh rate      | > 10x normal    | Possible token theft    |
| JWKS fetch errors       | Any             | IdP connectivity issue  |
| Redis connection errors | Any             | Storage unavailable     |
| Token issuance latency  | > 500ms         | Performance degradation |

### Logging Configuration

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import { FrontMcp, LogLevel } from '@frontmcp/sdk';

@FrontMcp({
  info: { name: 'Server', version: '1.0.0' },
  logging: {
    level: LogLevel.INFO,
    // Don't log tokens or secrets
    redact: ['access_token', 'refresh_token', 'client_secret'],
  },
})
export class Server {}
```

***

## Common Production Issues

<AccordionGroup>
  <Accordion title="Tokens invalid after deployment">
    **Cause:** Auto-generated keys changed after restart.

    **Solution:** Provide persistent signing keys via environment variable:

    ```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    local: {
      signKey: JSON.parse(process.env.JWT_SIGNING_KEY!),
    }
    ```
  </Accordion>

  <Accordion title="Session not found errors across instances">
    **Cause:** Using in-memory storage with multiple instances.

    **Solution:** Configure shared Redis storage:

    ```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    tokenStorage: {
      redis: {
        host: process.env.REDIS_HOST!,
        port: parseInt(process.env.REDIS_PORT || '6379'),
        password: process.env.REDIS_PASSWORD,
        tls: true,
      },
    }
    ```
  </Accordion>

  <Accordion title="JWKS fetch fails intermittently">
    **Cause:** Network issues or IdP rate limiting.

    **Solution:**

    * Increase JWKS cache TTL
    * Consider inline JWKS for critical IdPs
    * Add retry logic with exponential backoff
  </Accordion>

  <Accordion title="High latency on token endpoint">
    **Cause:** Redis connection issues or key generation overhead.

    **Solution:**

    * Use Redis connection pooling
    * Pre-generate signing keys (don't generate on-demand)
    * Monitor Redis latency
  </Accordion>
</AccordionGroup>

***

## Security Hardening

### Disable Insecure Options

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
auth: {
  mode: 'local',

  // Production settings
  allowDefaultPublic: false,   // No anonymous access

  // Use local mode login page or remote IdP
  // Never use the demo login page in production
}
```

For remote mode, DCR is configured under `providerConfig`:

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
auth: {
  mode: 'remote',
  provider: 'https://auth.example.com',
  clientId: process.env.IDP_CLIENT_ID!,
  clientSecret: process.env.IDP_CLIENT_SECRET!,
  providerConfig: {
    dcrEnabled: false,  // Disable DCR in production; pre-register clients
  },
}
```

### HTTP Security Headers

Configure your reverse proxy to add security headers:

```nginx theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
# Example nginx configuration
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Content-Security-Policy "default-src 'self'" always;
```

### Rate Limiting

FrontMCP includes a built-in **session rate limiter** with a sliding window algorithm:

| Setting          | Default      | Description                         |
| ---------------- | ------------ | ----------------------------------- |
| Window           | 60 seconds   | Sliding window duration             |
| Max requests     | 100          | Maximum requests per window per key |
| Cleanup interval | Configurable | Auto-cleanup of expired entries     |

The rate limiter tracks requests per key (typically client IP) and provides:

* `wouldAllow(key)` — pre-check without consuming quota
* `getStats()` — monitoring statistics

For external rate limiting on the token endpoint:

```nginx theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
# Rate limit token endpoint
location /oauth/token {
    limit_req zone=token_limit burst=10 nodelay;
    proxy_pass http://backend;
}
```

***

## Deployment Patterns

<Tabs>
  <Tab title="Single Instance">
    ```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    // Simple deployment - in-memory works but not recommended
    auth: {
      tokenStorage: 'memory', // Data lost on restart
    }
    ```

    <Warning>
      In-memory storage loses all sessions on restart. Users must re-authenticate.
    </Warning>
  </Tab>

  <Tab title="Multi-Instance">
    ```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    // Required for horizontal scaling
    auth: {
      tokenStorage: {
        redis: {
          host: process.env.REDIS_HOST!,
          port: parseInt(process.env.REDIS_PORT || '6379'),
          password: process.env.REDIS_PASSWORD,
          tls: true,
        },
      },
    }
    ```

    All instances must share the same Redis and signing keys.

    <Info>
      **Machine ID**: Set the `MACHINE_ID` environment variable to the same value on all instances. Without it, sessions encrypted on one instance cannot be decrypted on another. See [Machine ID](/frontmcp/deployment/machine-id).
    </Info>
  </Tab>

  <Tab title="Stateless">
    ```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    // No shared state needed - tokens contain all info
    auth: {
      local: {
        signKey: JSON.parse(process.env.JWT_SIGNING_KEY!),
      },
    }
    ```

    Simpler architecture but larger tokens, no silent refresh.
  </Tab>
</Tabs>

***

## Quick Reference

### OAuth 2.1 Compliance Checklist

<Check>PKCE required for all clients (S256 only)</Check>
<Check>Refresh token rotation enabled</Check>
<Check>No implicit grant flow</Check>
<Check>Authorization codes single-use</Check>
<Check>Redirect URI exact match</Check>
<Check>State parameter required</Check>

### Pre-Production Verification

```bash theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
# Test token endpoint
curl -X POST https://your-server/oauth/token \
  -d "grant_type=authorization_code" \
  -d "code=test-code" \
  -d "client_id=your-client" \
  -d "code_verifier=verifier"

# Verify JWKS endpoint
curl https://your-server/.well-known/jwks.json

# Check server metadata
curl https://your-server/.well-known/oauth-authorization-server
```

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Authorization Modes" icon="layer-group" href="/frontmcp/authentication/modes">
    Choose the right mode for your deployment
  </Card>

  <Card title="Tokens & Sessions" icon="key" href="/frontmcp/authentication/token">
    Configure token lifetimes and storage
  </Card>

  <Card title="Local OAuth" icon="server" href="/frontmcp/authentication/local">
    Built-in OAuth server setup
  </Card>

  <Card title="Remote OAuth" icon="cloud" href="/frontmcp/authentication/remote">
    External IdP integration
  </Card>
</CardGroup>
