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

OrchestratedTokenStore Interface

When using orchestrated mode with federated authentication, FrontMCP stores provider tokens using the OrchestratedTokenStore interface.

Interface Methods

MethodDescription
storeTokens(authorizationId, providerId, tokens)Store tokens for a provider
getTokens(authorizationId, providerId)Retrieve tokens for a provider
deleteTokens(authorizationId, providerId)Delete tokens for a provider
getProviderIds(authorizationId)Get all provider IDs with stored tokens
migrateTokens(fromAuthId, toAuthId)Migrate tokens between authorization IDs

Token Migration

The migrateTokens() method is used internally during federated auth completion to transfer tokens from a pending authorization ID to the final authorization ID:
import { InMemoryOrchestratedTokenStore } from '@frontmcp/sdk';

const tokenStore = new InMemoryOrchestratedTokenStore();

// During federated auth, tokens are stored with pending ID
await tokenStore.storeTokens('pending:abc123', 'github', {
  accessToken: 'gho_xxx',
  refreshToken: 'ghr_xxx',
});

// After JWT issuance, tokens are migrated to real auth ID
await tokenStore.migrateTokens('pending:abc123', 'user:real-auth-id');

// Query which providers have tokens
const providerIds = await tokenStore.getProviderIds('user:real-auth-id');
// => ['github']
migrateTokens() is called automatically during the OAuth token exchange flow when completing federated authentication.

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": "user@example.com"
  }
}

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

Authorization Modes

Choose the right auth mode for your use case

Progressive Authorization

Implement incremental app authorization

Production Checklist

Security requirements for deployment

Remote OAuth

Connect to external identity providers