Skip to main content

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.

Progressive authorization allows users to authorize apps incrementally, rather than all at once. This improves UX by only requesting access when tools actually need it.

How It Works


Configuration

Enable progressive auth with local mode:
@FrontMcp({
  info: { name: 'MyServer', version: '1.0.0' },
  auth: {
    mode: 'local',
    consent: { enabled: true }, // Enable consent UI
  },
  apps: [SlackApp, GitHubApp, CrmApp],
})
export class Server {}

Authorization Hierarchy

Progressive auth operates at three levels:

Token Vault

The token vault stores per-app credentials and expands as users authorize more apps:

Initial State

Session Token: user-123Vault:
  • CRM: Authorized

After Slack Auth

Session Token: user-123 (same)Vault:
  • CRM: Authorized
  • Slack: Authorized

After GitHub Auth

Session Token: user-123 (same)Vault:
  • CRM: Authorized
  • Slack: Authorized
  • GitHub: Authorized
The session token remains the same. Only the token vault expands with new app credentials.

Authorization Response

When a tool requires unauthorized access, FrontMCP returns:
{
  "error": "authorization_required",
  "code": "AUTH_REQUIRED",
  "app": "slack",
  "tool": "slack:send_message",
  "required_scopes": ["chat:write"],
  "auth_url": "https://my-server/oauth/authorize?app=slack&scope=chat:write",
  "message": "Slack authorization required",
  "hint": "Click the authorization link to grant access to Slack"
}

Handling in Clients

try {
  const result = await mcpClient.callTool('slack:send_message', { message: 'Hello' });
} catch (error) {
  if (error.code === 'AUTH_REQUIRED') {
    // Show auth link to user
    console.log(`Please authorize: ${error.auth_url}`);
  }
}

The built-in consent UI lets users choose which apps to authorize:
+----------------------------------------------------------+
|                    Authorize Access                        |
|                                                            |
|  MyApp requests access to the following services:          |
|                                                            |
|  +------------------------------------------------------+  |
|  |  CRM (Auth0)                           [Authorized]  |  |
|  |  Tools: get_contacts, update_contact                 |  |
|  +------------------------------------------------------+  |
|                                                            |
|  +------------------------------------------------------+  |
|  |  Slack                                    [Skipped]  |  |
|  |  Tools: send_message, list_channels                  |  |
|  |                                                      |  |
|  |  [ Authorize Later ]                                 |  |
|  +------------------------------------------------------+  |
|                                                            |
|  +------------------------------------------------------+  |
|  |  GitHub                                   [Pending]  |  |
|  |  Tools: create_issue, list_repos                     |  |
|  |                                                      |  |
|  |  [ Authorize ]  [ Skip ]                             |  |
|  +------------------------------------------------------+  |
|                                                            |
|            [ Continue with authorized apps ]               |
+----------------------------------------------------------+

Multi-Provider Setup

App Configuration

@App({
  name: 'Slack',
  auth: {
    mode: 'transparent',
    provider: 'https://slack.com/oauth',
    scopes: ['chat:write', 'channels:read'],
  },
})
export class SlackApp {
  @Tool({ name: 'send_message' })
  async sendMessage(ctx: ToolContext, input: { message: string }) {
    // Uses Slack token from vault
  }
}

@App({
  name: 'GitHub',
  auth: {
    mode: 'transparent',
    provider: 'https://github.com/login/oauth',
    scopes: ['repo', 'user'],
  },
})
export class GitHubApp {
  @Tool({ name: 'create_issue' })
  async createIssue(ctx: ToolContext, input: { title: string }) {
    // Uses GitHub token from vault
  }
}

Server Configuration

@FrontMcp({
  info: { name: 'AgentSuite', version: '1.0.0' },
  auth: {
    mode: 'local',
    consent: { enabled: true },
    tokenStorage: {
      redis: {
        host: process.env.REDIS_HOST!,
        port: parseInt(process.env.REDIS_PORT || '6379'),
      },
    },
  },
  apps: [SlackApp, GitHubApp],
})
export class Server {}

Standalone vs Nested Apps

Apps can be configured as standalone (direct access) or nested (under parent):
ConfigurationDirect AccessFederated Auth
standalone: true/slack/oauth/authorizeAlso in parent consent
standalone: false (default)N/AOnly via parent
@App({
  name: 'Slack',
  standalone: true, // Direct access at /slack
  auth: { /* ... */ },
})
export class SlackApp {}

Skip and Authorize Later

Users can skip apps during initial consent and authorize later:

Skipping

// User skips Slack during initial auth
// Session created with: authorized_apps: ['crm'], skipped_apps: ['slack']

Later Authorization

GET /oauth/authorize?app=slack&prompt=consent
This triggers a targeted authorization flow for just the skipped app.

Session Token Structure

{
  "sub": "user-123",
  "iss": "https://my-server",
  "iat": 1234567890,
  "exp": 1234571490,
  "session_id": "sess_abc123",
  "authorized_apps": ["crm", "billing"],
  "pending_apps": ["slack", "github"],
  "scopes": ["crm:read", "crm:write", "billing:read"]
}
Child tokens are stored in the Token Vault (server-side), not embedded in the JWT.

OpenAPI Adapter Integration

When using OpenAPI adapters, tools are automatically grouped by auth provider:
@FrontMcp({
  info: { name: 'APIGateway', version: '1.0.0' },
  auth: {
    mode: 'local',
    consent: { enabled: true },
  },
  adapters: [
    {
      type: 'openapi',
      spec: 'https://api.github.com/openapi.json',
      auth: {
        mode: 'transparent',
        provider: 'https://github.com',
        clientId: 'github-client-id',
      },
    },
    {
      type: 'openapi',
      spec: 'https://api.stripe.com/openapi.json',
      auth: {
        mode: 'transparent',
        provider: 'https://connect.stripe.com',
        clientId: 'stripe-client-id',
      },
    },
  ],
})
export class Server {}
Tools from each adapter are grouped by their auth configuration and appear in the consent UI accordingly.
When consent is enabled, FrontMCP tracks granular tool-level authorization using these types:

ConsentToolItem

Represents a tool in the consent UI:
FieldTypeDescription
idstringTool identifier
namestringDisplay name
descriptionstringTool description
appIdstringParent app ID
requiredScopesstring[]OAuth scopes needed
categorystringGrouping category

ConsentSelection

Captures the user’s tool selection:
FieldTypeDescription
selectedToolsstring[]Tool IDs the user authorized
allSelectedbooleanWhether all tools were selected
consentedAtnumberTimestamp of consent
consentVersionstringSchema version for migration

ConsentState

Full consent flow state passed to the consent UI:
FieldTypeDescription
availableToolsConsentToolItem[]All tools requiring consent
preSelectedstring[]Tools pre-selected by default
groupBystringGrouping field (e.g., appId, category)

FederatedLoginState

For multi-provider consent where users select which IdPs to authenticate with:
FieldTypeDescription
providersFederatedProviderItem[]Available identity providers
requiredstring[]Providers that cannot be skipped
optionalstring[]Providers the user can skip

Best Practices

Request minimal scopes - Only ask for what each tool needs
Provide clear descriptions - Users should understand why each app is needed
Handle auth errors gracefully - Show friendly messages with auth links
Use stateful sessions - Required for token vault to work
Test the skip flow - Ensure skipped apps can be authorized later

Troubleshooting

  • Ensure stateful token storage is configured (Redis for multi-instance)
  • Check Redis connectivity if using Redis storage
  • Verify the session ID matches across requests
  • Use prompt=consent to force the consent screen
  • Check that the app wasn’t excluded via excludeFromParent

Next Steps

Remote OAuth

Configure external identity providers

Tokens & Sessions

Token lifecycle and session management

Production Checklist

Security requirements for deployment

Authorization Modes

Choose the right auth mode