Skip to main content
FrontMCP provides flexible token and session management with support for both stateful and stateless patterns.

Token Types

TokenPurposeLifetimeStorage
Access TokenAPI authorization1 hour (default)Client-side (JWT)
Refresh TokenObtain new access tokens30 days (default)Server-side
Authorization CodeOAuth flow exchange60 secondsServer-side, single-use
Session TokenTrack user sessionConfigurableDepends on mode

Session Modes

FrontMCP supports two session management strategies:

Stateful Sessions

Tokens stored server-side. Clients hold lightweight references.Pros:
  • Silent token refresh
  • Revocation without client update
  • Secure token storage
Cons:
  • Requires shared storage (Redis)
  • State management complexity

Stateless Sessions

All data embedded in JWT. No server-side storage.Pros:
  • Horizontally scalable
  • No shared state
  • Simple architecture
Cons:
  • No silent refresh
  • Larger token size
  • Can’t revoke without expiry

Stateful Session Configuration

@FrontMcp({
  info: { name: 'MyServer', version: '1.0.0' },
  auth: {
    mode: 'orchestrated',
    type: 'local',
    sessionMode: 'stateful',
    tokenStorage: {
      type: 'redis',
      config: {
        host: 'localhost',
        port: 6379,
        keyPrefix: 'myapp:auth:',
      },
    },
  },
})
export class Server {}

Stateless Session Configuration

@FrontMcp({
  info: { name: 'MyServer', version: '1.0.0' },
  auth: {
    mode: 'orchestrated',
    type: 'local',
    sessionMode: 'stateless',
  },
})
export class Server {}
Stateful sessions require shared storage when running multiple server instances. Without Redis, each instance maintains its own session state.

Token Storage

In-Memory (Development)

tokenStorage: {
  type: 'memory',
}
In-memory storage loses all data on restart. Use only for development.

Redis (Production)

tokenStorage: {
  type: 'redis',
  config: {
    host: 'redis.example.com',
    port: 6379,
    password: process.env.REDIS_PASSWORD,
    keyPrefix: 'auth:',
    tls: true,
  },
}

Storage Contents

Key PatternDataTTL
{prefix}pending:{id}Pending authorization10 minutes
{prefix}code:{code}Authorization code60 seconds
{prefix}refresh:{token}Refresh token30 days
{prefix}session:{id}Session dataConfigurable

Token Lifetimes

FrontMCP uses default token lifetimes:
Token TypeDefault Lifetime
Access Token1 hour
Refresh Token30 days
Authorization Code60 seconds

Token Refresh Configuration

auth: {
  mode: 'orchestrated',
  type: 'local',
  refresh: {
    enabled: true,      // Enable automatic token refresh
    skewSeconds: 60,    // Refresh 60s before expiry
  },
}
Token lifetimes are currently set at the server level. Refresh tokens are rotated on each use per OAuth 2.1 best practices.

Token Refresh Flow

Refresh tokens are rotated on each use (OAuth 2.1 best practice):

JWT Structure

Access tokens are JWTs with standard claims:
{
  "header": {
    "alg": "RS256",
    "typ": "JWT",
    "kid": "key-id-123"
  },
  "payload": {
    "sub": "user-uuid",
    "iss": "https://api.myservice.com",
    "aud": "https://api.myservice.com",
    "exp": 1234567890,
    "iat": 1234567800,
    "jti": "unique-token-id",
    "scope": "read write",
    "email": "[email protected]"
  }
}

Custom Claims

Add custom claims to tokens:
auth: {
  mode: 'orchestrated',
  type: 'local',
  tokenClaims: async (user) => ({
    roles: user.roles,
    tenant_id: user.tenantId,
  }),
}

Enable consent to let users select granted permissions:
auth: {
  mode: 'orchestrated',
  type: 'local',
  consent: { enabled: true },
}
  1. User authenticates
  2. FrontMCP displays available tools/resources/prompts
  3. User selects which to grant
  4. Access token includes only selected scopes

Tool-Level Scopes

@Tool({
  name: 'send_message',
  description: 'Send a message',
  scopes: ['messages:write'], // Required scope
})
export class SendMessageTool {
  async execute(ctx: ToolContext) {
    // Only callable if token has messages:write scope
  }
}

JWKS Management

FrontMCP manages cryptographic keys for token signing:

Auto-Generated Keys

By default, keys are auto-generated at startup:
auth: {
  mode: 'orchestrated',
  type: 'local',
  // Keys auto-generated
}
Auto-generated keys are lost on restart. Tokens signed with old keys become invalid.

Persistent Keys

Provide keys for stable token validation across restarts:
auth: {
  mode: 'orchestrated',
  type: 'local',
  local: {
    signKey: {
      kty: 'RSA',
      // ... full JWK
    },
    jwks: {
      keys: [/* public keys */],
    },
  },
}

Key Rotation

For production, implement key rotation:
auth: {
  mode: 'orchestrated',
  type: 'local',
  local: {
    keyRotationDays: 30, // Rotate every 30 days
    maxKeys: 3, // Keep 3 keys for validation
  },
}

Token Verification

Verification Flow

Verification Options

auth: {
  mode: 'transparent',
  remote: {
    provider: 'https://auth.example.com',
  },
  expectedAudience: 'https://api.myservice.com',
  requiredScopes: ['openid', 'profile'],
  clockTolerance: 30, // seconds of clock skew allowed
}

Error Responses

Token-related errors follow OAuth 2.0 error format:
ErrorHTTP StatusDescription
invalid_token401Token expired, malformed, or invalid signature
insufficient_scope403Token missing required scopes
invalid_request400Malformed token request
invalid_grant400Invalid authorization code or refresh token

Example Error Response

{
  "error": "invalid_token",
  "error_description": "Token has expired",
  "error_uri": "https://tools.ietf.org/html/rfc6750#section-3.1"
}

Next Steps