ZuploZuplo
LoginStart for Free
  • Documentation
  • API Reference
Getting Started
Concepts
API Management
AI Gateway
MCP Gateway
    Overview
    Getting Started
      How it works
      Connect MCP clients
      Authentication
        OverviewUpstream OAuthConnect an upstream OAuth providerConnect an API-key upstream
        Identity providers
          Auth0Amazon CognitoClerkMicrosoft EntraGoogleKeycloakLogtoOktaOneLoginPingOneWorkOSGeneric OIDC
        Manual OAuth testing
      Cross App Access
      Configuration
      Policies
      Observability
      ReferenceServer RegistryTroubleshooting
    MCP Server
    Developer Portal
    Development
    Deploying & Source Control
    Analytics
    Observability
    Networking & Infrastructure
    Account Management
    Programming API
    Build with AI
    Zuplo CLI
    Migration Guides
    Platform LimitsVersion Support PolicySecuritySupportTrust & ComplianceChangelog
    powered by Zuplo
    Identity providers

    Configuring Okta

    The MCP Gateway can use Okta as the identity provider behind its downstream OAuth flow. The mcp-okta-oauth-inbound policy is an Okta-friendly wrapper around the generic mcp-oauth-inbound policy: provide your Okta domain, a client ID, and a client secret, and the policy derives the OIDC issuer, JWKS URL, and authorize and token URLs for you.

    This guide walks through the Okta admin console setup, then wires the policy into a gateway project. Read the authentication overview first for the two-layer model and the role each policy plays.

    The wrapper supports both Okta's org authorization server (the default) and custom authorization servers. Custom authorization servers give you control over scopes and audiences; the org server is fine for the gateway's identity-only use of Okta.

    Set up Okta

    The MCP Gateway acts as an OAuth 2.1 authorization server in front of Okta. Okta handles browser login and identity; the gateway issues its own access tokens that bind to MCP routes. The Okta application you create represents the gateway's identity against Okta, not the MCP client.

    Create an OIDC application

    1. In the Okta Admin Console, open Applications → Applications and click Create App Integration.
    2. Choose OIDC - OpenID Connect as the sign-in method and Web Application as the application type. Click Next.
    3. Give the integration a name (for example, Zuplo MCP Gateway).
    4. Under Grant types, leave Authorization Code checked. The gateway does not need refresh tokens from Okta — it uses Okta only for browser identity, not as a long-running token source.
    5. Set Sign-in redirect URIs to your gateway's https://<gateway-host>/__zuplo/oauth/callback. Add http://localhost:9000/__zuplo/oauth/callback for local development with zuplo dev.
    6. Under Assignments, restrict access to the groups or users who should be able to authenticate against the gateway.
    7. Click Save.

    Note the Client ID and Client Secret from the application's General tab. You'll wire these into the policy in the next section.

    Optional: pick a custom authorization server

    By default the policy uses the Okta org authorization server, which is enough for gateway browser identity. If you already operate a custom authorization server, open Security → API → Authorization Servers in the Okta admin console and note the server's name (such as default or customer-portal). You'll pass that name as the authorizationServerId option.

    Wire the policy into the gateway

    Add the policy to config/policies.json:

    Code
    { "name": "okta-managed-oauth", "policyType": "mcp-okta-oauth-inbound", "handler": { "module": "$import(@zuplo/runtime/mcp-gateway)", "export": "McpOktaOAuthInboundPolicy", "options": { "oktaDomain": "$env(OKTA_DOMAIN)", "clientId": "$env(OKTA_CLIENT_ID)", "clientSecret": "$env(OKTA_CLIENT_SECRET)" } } }

    oktaDomain is a bare hostname like acme.okta.com or acme.oktapreview.com. Don't include https://, a trailing slash, or an /oauth2/... path.

    Set the three environment variables in your project's environment configuration. OKTA_DOMAIN goes in plain config; the secret values belong in the project secret store.

    To use a custom authorization server, add authorizationServerId:

    Code
    { "options": { "oktaDomain": "$env(OKTA_DOMAIN)", "authorizationServerId": "default", "clientId": "$env(OKTA_CLIENT_ID)", "clientSecret": "$env(OKTA_CLIENT_SECRET)" } }

    Attach the policy to each MCP route in config/routes.oas.json:

    Code
    { "paths": { "/mcp/linear-v1": { "get,post": { "operationId": "linear-mcp-server", "x-zuplo-route": { "corsPolicy": "none", "handler": { "module": "$import(@zuplo/runtime/mcp-gateway)", "export": "McpProxyHandler", "options": { "rewritePattern": "https://mcp.linear.app/mcp", }, }, "policies": { "inbound": ["okta-managed-oauth", "mcp-token-exchange-linear"], }, }, }, }, }, }

    Register the gateway plugin in modules/zuplo.runtime.ts:

    Code
    import { RuntimeExtensions } from "@zuplo/runtime"; import { McpGatewayPlugin } from "@zuplo/runtime/mcp-gateway"; export function runtimeInit(runtime: RuntimeExtensions) { runtime.addPlugin(new McpGatewayPlugin()); }

    One MCP OAuth policy serves every MCP route in the project — attach the same policy by name to each route.

    Full options reference

    OptionRequiredDefaultNotes
    oktaDomainyes—Bare hostname (acme.okta.com). No scheme.
    authorizationServerIdnounset (org server)Name of an Okta custom authorization server, e.g. default.
    clientIdyes—Okta application client ID.
    clientSecretyes—Okta application client secret. Use $env(...).
    scopenoopenid profile emailOIDC scopes requested during browser login.
    gateway.accessTokenTtlSecondsno900Gateway-issued access token lifetime.
    gateway.refreshTokenTtlSecondsnolong-livedGateway-issued refresh token lifetime. Override only if you need to shorten sessions.
    gateway.cimdEnablednotrueAdvertise CIMD support in AS metadata.
    browserLoginOverrides.sessionTtlSecondsno28800Browser session cookie lifetime (8 hours).
    browserLoginOverrides.stateTtlSecondsno900Browser-login state record lifetime.
    browserLoginOverrides.remoteTimeoutMsno10000Outbound timeout to Okta (token exchange, JWKS fetch).

    Test the configuration

    The fastest sanity check is to connect an MCP client:

    1. Open Claude Desktop, Cursor, Claude Code, or another OAuth-aware MCP client.
    2. Add a remote MCP server pointing at one of your /mcp/{slug} routes on the gateway.
    3. The client should redirect you to Okta's login page. After login, the gateway's consent screen renders. Approve it.
    4. The client receives an access token and can call tools/list.

    If something fails partway through, walk the flow manually using the manual OAuth testing guide — it exercises every endpoint with curl so you can see the raw responses.

    Common issues

    • "Invalid Okta domain" at boot. oktaDomain includes a scheme prefix, trailing slash, or path. Use acme.okta.com.
    • Browser login redirects but the callback fails. The https://<gateway-host>/__zuplo/oauth/callback URL isn't on the application's Sign-in redirect URIs allow-list in Okta.
    • invalid_audience from the gateway's token endpoint. The MCP client is reusing a token bound to a different route. Each gateway-issued token binds to one MCP route.
    • MCP client can't discover the AS. Confirm the mcp-okta-oauth-inbound policy is attached to the route in routes.oas.json and the McpGatewayPlugin is registered in modules/zuplo.runtime.ts. The internal OAuth endpoints register only when both are present.

    Related

    • Authentication overview
    • Configuring Auth0 — the most-used wrapper
    • Configuring a generic OIDC provider — for IdPs without a first-class wrapper.
    • Per-user OAuth to upstream MCP servers
    Edit this page
    Last modified on August 20, 2026
    LogtoOneLogin
    On this page
    • Set up Okta
      • Create an OIDC application
      • Optional: pick a custom authorization server
    • Wire the policy into the gateway
    • Full options reference
    • Test the configuration
    • Common issues
    • Related
    JSON
    JSON
    JSON
    TypeScript