Skip to main content
FrontMCP’s five authentication modes address different deployment scenarios. Understanding when to use each mode is critical for both security and developer experience.

Mode Overview

The auth.mode literal accepts one of five values: 'public', 'static', 'transparent', 'local', 'remote'.

Mode Comparison


Public Mode

No authentication required. All requests receive an anonymous session.

How It Works

Configuration Options

Use Cases

Development

Rapid prototyping without auth setup overhead

Public APIs

Endpoints that don’t require user identity
Do NOT use public mode when you need:
  • User identity tracking
  • Audit trails
  • Access control per user
  • Compliance requirements
Bearer tokens in public mode. A JWT is still verified against this instance’s own HS256 secret, so a valid gateway token upgrades the request to that identity. A bearer token that is not a JWT is ignored and the request is served anonymously — public mode has no issuer, JWKS or audience to verify it against, and a request carrying a credential must never fare worse than the same request without one.That also means a @Will('checkAuthorization') hook can implement its own shared-secret check without having to strip the header before the built-in verifier sees it. For a first-class shared secret, prefer static mode.
The session id is the anonymous caller’s only credential. Every anonymous session shares an empty token, so the server honors an mcp-session-id only when it decrypts under this deployment’s MCP_SESSION_SECRET. An id minted under another secret — another deployment sharing the Redis, or a secret you rotated — is answered with HTTP 404 and the client re-initializes. Run every instance with the same MCP_SESSION_SECRET: any of them then serves a session another one minted. A client that sends initialize again on a different instance starts a new session there, rather than a second transport for the old id.

Static Mode

A fixed shared secret presented on every request — the shape every non-OAuth MCP host expects. ChatGPT’s custom-app connector calls it Access token / API key: it attaches a constant Authorization: Bearer <token> and never performs an OAuth exchange.
Use it when you want a server protected by one secret without standing up an OAuth 2.1 provider. Before this mode existed the only options were “no auth at all” or a full OAuth server — and for a server wrapping a billed third-party API, “no auth” means anyone who learns the URL can spend your money.

How It Works

  1. The credential is read from the configured header (authorization by default), and the scheme prefix (Bearer by default, matched case-insensitively) is stripped.
  2. It is compared against each configured token in constant time, over SHA-256 digests, so neither the secret’s value nor its length is observable through timing.
  3. A match produces an authenticated session whose sub is static:<12 hex chars> — a non-reversible digest prefix of the token that matched, so audit logs and per-subject partitions (rate limits, sessions) can tell configured tokens apart without the secret appearing anywhere.
  4. Anything else answers 401 with a WWW-Authenticate: Bearer realm="…" challenge. Unlike public mode, a missing credential is also a 401.

Configuration Options

An API-key header with no scheme prefix:
A shared secret identifies a caller, not a user. There is no per-user identity, no revocation short of rotating the token and redeploying, and no progressive auth. Use local or remote when you need any of those. Rotate by deploying with both the old and new token in tokens, then removing the old one.
Static mode is checked entirely in the session:verify flow’s handleStaticToken stage, before any other authentication stage runs. The server still exposes the OAuth discovery endpoints that public mode does; nothing uses them in this mode.

Transparent Mode

Pass-through tokens from an external identity provider. FrontMCP validates tokens but doesn’t issue them.

How It Works

Configuration Options

Claim validation

A valid signature only proves the IdP’s key signed the token — not that the token was minted for this server. Because every service behind the same IdP shares the same signing keys, FrontMCP enforces two claims on top of the signature:
  • Issuer (iss) — must equal provider (matched with or without a trailing slash) or one of providerConfig.additionalIssuers. Enabled by default. This stops a token minted by the same IdP for a different issuer from being replayed here.
  • Audience (aud) — if the token carries an aud, it must match expectedAudience (or the derived resource URL). This stops a token issued for service A from being replayed against service B. Tokens with no aud claim are accepted for IdP compatibility; set expectedAudience and issue audience-bound tokens for the strictest posture.
  • Expiry (exp) — required. A token without exp would never expire, so it is refused (401 whose error_description says exp is missing).
providerConfig.verifyIssuer: false disables issuer validation entirely. Any token signed by a key in the provider JWKS is then accepted regardless of its iss. Only use it for a trusted gateway that re-mints tokens under an issuer you cannot enumerate with additionalIssuers, and always pair it with a strict expectedAudience. Whenever the issuer set is known, prefer additionalIssuers over disabling the check.

Provider Examples

Use Cases

Existing IdP Integration

Your organization already uses Auth0, Okta, or similar

Single Provider

All users authenticate through one identity provider
Do NOT use transparent mode when you need:
  • Multiple identity providers
  • Progressive authorization (add apps over time)
  • Server-side token storage with silent refresh
  • Custom token claims

Local Mode

FrontMCP acts as a full OAuth 2.1 authorization server with a built-in login form. Self-contained auth server with built-in user management.
Local mode signs the tokens it issues with HS256 using the JWT_SECRET environment variable (no RSA/EC key pair). See Local OAuth for token signing, persistent tokenStorage (memory / sqlite / redis), the requireEmail / anonymousSubject single-operator options, and tunnel/issuer configuration. Local mode also orchestrates multiple upstream OAuth providers out of the box. Declare a providers array (GitHub, Slack, Jira, …) and FrontMCP federates them at /oauth/authorize, refuses to mint a JWT until federatedAuth.minProviders (default 1) are linked, stores each provider’s tokens encrypted server-side, and exposes them to tools via this.orchestration.getToken(id):
See Multi-Provider Orchestration for the full provider schema, the minProviders / requiredProviders gate, and the this.orchestration tool API.
The built-in login page accepts any email format without validation and is intended for development. Replace with a real identity provider for production use.

Remote Mode

FrontMCP acts as an OAuth 2.1 authorization server that proxies user authentication to an upstream IdP. End users never submit credentials to FrontMCP directly — they’re redirected to the upstream IdP, and FrontMCP exchanges the resulting upstream code/tokens before issuing its own session token to the MCP client.

How It Works

The upstream identity comes from the IdP’s id_token only when it verifies: signed by a key the provider publishes (providerConfig.jwks, else providerConfig.jwksUri, else <provider>/.well-known/jwks.json, else the jwks_uri of the provider’s OAuth metadata or /.well-known/openid-configuration), issued by provider (or providerConfig.additionalIssuers), for clientId, and not expired. Otherwise FrontMCP asks the userinfo endpoint. A callback whose RFC 9207 iss names another server, and a sign-in the user declined at the IdP (error=access_denied), end with an error page and no code.

Configuration Options

Remote mode and unregistered clients. Remote mode has no dcr block (no pre-registered clients) and its /oauth/register is off in production, so with the default requireRegisteredClients: true a production remote server admits only MCP clients that use a CIMD client-id URL. In 1.8.2 and earlier an unregistered client_id was accepted; see MCP client registration for the migration.
When consent.enabled is true, the local flow renders an interactive tool-selection screen after login and enforces the selection at call time: the chosen tool ids are embedded in the token’s consent claim and a tools/call to an unselected tool is rejected with TOOL_NOT_CONSENTED (JSON-RPC -32003). The flags groupByApp, showDescriptions, allowSelectAll, requireSelection, customMessage, excludedTools, and defaultSelectedTools are all honored. See Local OAuth → Consent for the full table.
Tokens minted without consent (consent disabled, or created via the test/programmatic factory) carry no consent claim and are unaffected — all tools remain callable. rememberConsent (default true) persists each user’s per-client selection and reuses it on the next login, re-prompting only when a NEW tool appears; set it false to always re-show the screen.

Incremental Authorization

Federated Authentication Configuration

Configure how multi-provider (federated) authentication is gated and validated.

OAuth Endpoints

Local and remote modes expose standard OAuth endpoints:

Use Cases

Multi-Provider Federation

Combine multiple IdPs under one session (Slack + GitHub + custom)

Progressive Authorization

Users authorize apps incrementally as needed

Full Token Control

Custom token lifetimes, scopes, and refresh behavior

Tool-Authorization Enforcement

Gate tools via the interactive consent picker (shipped) and enforce the selection at call time
Do NOT use local or remote mode when:
  • You only have one IdP and don’t need federation (use transparent instead)
  • You want to minimize auth complexity
  • Running multiple instances without Redis

Discovery and Sessions by Mode

The protected resource metadata (/.well-known/oauth-protected-resource) advertises in scopes_supported the scopes a client can actually be given here: Outside local and remote mode, the scopes entries declare on their authProviders are listed too. A server that names no scope leaves scopes_supported out. The issuer named in discovery, on authorization responses and in tokens is the same for every request; see The issuer. A session client (protocol revisions before 2026-07-28) resumes its session with the mcp-session-id header, or the ?sessionId= query parameter on the legacy SSE /message endpoint. The server serves the request under that id only when session:verify verified it: in public, static and anonymous transparent mode the id must decrypt under MCP_SESSION_SECRET and carry the signature of the mode (in static mode, of the token that opened it); in the authenticated modes it must also belong to the caller’s token. Any other id is answered with HTTP 404 (-32000), which tells the client to send initialize again; it is never used to look up a transport. An initialize that presents an unverified id starts a fresh session. An MCP 2026-07-28 request has no session. An anonymous or static-key caller is identified for that request only (a fresh anon: subject, or static:<digest>), and no session is minted for it, so MCP_SESSION_SECRET is needed only for session clients (protocol revisions before 2026-07-28). In a tool, this.context.sessionId and this.authInfo.sessionId name the request, and this.context.verifiedSessionId is undefined: key anything that must outlive the request on the authenticated caller.

Mode Selection Flowchart


Security Comparison


Token Verification Flow


Next Steps

Remote OAuth

Configure upstream IdP integration

Local OAuth

Set up self-contained authentication

Progressive Authorization

Implement incremental app authorization

Production Deployment

Security checklist for production