Skip to main content
FrontMCP includes a built-in OAuth 2.1 authorization server for self-contained authentication scenarios.
The built-in login page accepts any email format without validation. Replace with a real identity provider for production use.

Basic Configuration

@FrontMcp({
  info: { name: 'MyServer', version: '1.0.0' },
  auth: {
    mode: 'orchestrated',
    type: 'local',
    consent: { enabled: true },
  },
})
export class Server {}

Configuration Options

OptionTypeDefaultDescription
consentConsentConfig{ enabled: false }Consent UI configuration
sessionMode'stateful' | 'stateless''stateful'Session management strategy
tokenStorageobject{ type: 'memory' }Token storage backend
allowDefaultPublicbooleanfalseAllow unauthenticated access
anonymousScopesstring[]['anonymous']Scopes for anonymous sessions
incrementalAuthIncrementalAuthConfig{ enabled: true }Progressive authorization config

OAuth Endpoints

Local mode exposes standard OAuth 2.1 endpoints:
EndpointMethodDescription
/oauth/authorizeGETStart authorization flow
/oauth/tokenPOSTExchange code for tokens
/oauth/registerPOSTDynamic Client Registration
/oauth/userinfoGETGet user profile
/.well-known/oauth-authorization-serverGETServer metadata
/.well-known/jwks.jsonGETPublic signing keys

Authorization Request

GET /oauth/authorize
  ?response_type=code
  &client_id=your-client-id
  &redirect_uri=http://localhost:3000/callback
  &scope=openid profile
  &state=random-state
  &code_challenge=base64url(sha256(verifier))
  &code_challenge_method=S256

Token Exchange

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=authorization-code
&redirect_uri=http://localhost:3000/callback
&client_id=your-client-id
&code_verifier=original-verifier

OAuth Callback Flow (/oauth/callback)

The callback endpoint processes the user’s response from the authorization page and creates an authorization code. It executes through the following stages:
StageDescription
parseInputExtract query parameters (email, pending_auth_id, etc.)
validatePendingAuthVerify pending auth record and validate providers
handleIncrementalAuthProcess progressive authorization for apps
handleFederatedAuthChain through federated identity providers
createAuthorizationCodeGenerate authorization code with consent data
redirectToClientRedirect user back to client with code

Callback Query Parameters

ParameterDescription
pending_auth_idReference to pending authorization record
emailUser’s email address
nameUser’s display name (optional)
incrementalSet to true for progressive auth (optional)
app_idTarget app for incremental authorization (optional)
federatedSet to true for federated login (optional)
providersSelected provider IDs (optional, array or string)
toolsSelected tool IDs for consent (optional, array)

Federated Provider Chaining

When federated=true and providers are selected:
  1. A federated session is created to track progress
  2. PKCE is generated for the first provider
  3. User is redirected to the first provider’s authorization URL
  4. After each provider completes, the chain continues to the next
  5. Once all providers complete, an authorization code is created
Selected provider IDs are validated against the pending authorization’s allowed providers. Invalid provider IDs are rejected with a 400 error.

Error Handling

  • Missing pending_auth_id: Returns 400 with error page
  • Expired authorization: Returns 400 with “request expired” message
  • Invalid provider selection: Returns 400 with “invalid provider selection” message
  • Missing required fields: Returns 500 with error page

Key Management

Auto-Generated Keys

By default, FrontMCP generates RS256 keys at startup:
auth: {
  mode: 'orchestrated',
  type: 'local',
  // Keys auto-generated
}
Auto-generated keys are lost on restart. Existing tokens become invalid.

Persistent Keys

Provide keys for stable token validation:
auth: {
  mode: 'orchestrated',
  type: 'local',
  local: {
    signKey: {
      kty: 'RSA',
      kid: 'my-key-id',
      alg: 'RS256',
      n: '...',  // RSA modulus
      e: 'AQAB',
      d: '...',  // Private exponent
      // ... other JWK parameters
    },
  },
}

ES256 Keys

Use ES256 for smaller tokens:
auth: {
  mode: 'orchestrated',
  type: 'local',
  local: {
    signKey: {
      kty: 'EC',
      crv: 'P-256',
      x: '...',
      y: '...',
      d: '...',
    },
  },
}

Dynamic Client Registration

DCR allows clients to register programmatically. DCR is enabled by default in development.

Registration Request

POST /oauth/register
Content-Type: application/json

{
  "redirect_uris": ["http://localhost:3000/callback"],
  "client_name": "My Application",
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"]
}

Registration Response

{
  "client_id": "generated-uuid",
  "client_id_issued_at": 1234567890,
  "redirect_uris": ["http://localhost:3000/callback"],
  "client_name": "My Application",
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"]
}
DCR is intended for development only. In production, pre-register clients. DCR only allows localhost redirect URIs by default.

Per-App Configuration

Configure local auth per app with splitByApp: true:
@App({
  name: 'Billing',
  auth: {
    mode: 'orchestrated',
    type: 'local',
    consent: { enabled: true },
  },
})
export class BillingApp {}

@App({
  name: 'Analytics',
  auth: {
    mode: 'orchestrated',
    type: 'local',
  },
})
export class AnalyticsApp {}

@FrontMcp({
  info: { name: 'Suite', version: '1.0.0' },
  apps: [BillingApp, AnalyticsApp],
  splitByApp: true,
})
export class Server {}

Token Storage

In-Memory (Development)

auth: {
  mode: 'orchestrated',
  type: 'local',
  tokenStorage: { type: 'memory' },
}

Redis (Production)

auth: {
  mode: 'orchestrated',
  type: 'local',
  tokenStorage: {
    type: 'redis',
    config: {
      host: process.env.REDIS_HOST!,
      port: parseInt(process.env.REDIS_PORT || '6379'),
      password: process.env.REDIS_PASSWORD,
      keyPrefix: 'myapp:auth:',
    },
  },
}

Enable and customize the consent UI:
auth: {
  mode: 'orchestrated',
  type: 'local',
  consent: {
    enabled: true,
    groupByApp: true,           // Group tools by app
    showDescriptions: true,     // Show tool descriptions
    allowSelectAll: true,       // Allow selecting all tools
    requireSelection: true,     // Require at least one tool
    rememberConsent: true,      // Remember for future sessions
    excludedTools: ['health'],  // Always available tools
    defaultSelectedTools: [],   // Pre-selected tools
    customMessage: 'Select the tools you want to grant access to.',
  },
}

Complete Example

import { FrontMcp } from '@frontmcp/sdk';

@FrontMcp({
  info: { name: 'MyServer', version: '1.0.0' },
  auth: {
    mode: 'orchestrated',
    type: 'local',
    consent: { enabled: true },
    sessionMode: 'stateful',
    tokenStorage: {
      type: 'redis',
      config: {
        host: process.env.REDIS_HOST!,
        port: parseInt(process.env.REDIS_PORT || '6379'),
        password: process.env.REDIS_PASSWORD,
      },
    },
    refresh: {
      enabled: true,
      skewSeconds: 60,
    },
    local: {
      signKey: JSON.parse(process.env.JWT_SIGNING_KEY!),
    },
    incrementalAuth: {
      enabled: true,
      allowSkip: true,
      skippedAppBehavior: 'require-auth',
    },
  },
})
export class Server {}

Troubleshooting

Auto-generated keys are lost on restart. Either:
  • Provide persistent keys via local.signKey
  • Use Redis for token storage
  • Accept that users must re-authenticate
Ensure you’re using S256 challenge method and the code_verifier matches the original code_challenge.
When running multiple server instances, use Redis for token storage to share session state.

Next Steps

Remote OAuth

Connect to external identity providers

Progressive Authorization

Implement incremental app authorization

Production Checklist

Security requirements for deployment

Tokens & Sessions

Configure token lifetimes