Skip to content
agentgateway has joined the Agentic AI FoundationLearn more

For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.

Page as Markdown

Okta

Protect MCP servers with Okta as the authorization server.

Okta is an enterprise identity platform. Agentgateway includes a native okta MCP authentication provider so that you can use Okta as the authorization server for your MCP servers.

In this guide, you create an authorization server and app integrations in Okta, protect a sample MCP server with the okta provider, and verify that agentgateway rejects unauthenticated requests and admits tokens that Okta issues.

Why the Okta provider is needed

MCP clients follow the MCP authorization specification, which relies on OAuth behaviors that Okta implements differently.

When you set provider.okta, agentgateway bridges these gaps as follows:

  • Serves authorization server metadata from Okta’s OpenID Connect discovery document, because Okta does not support the RFC 8414 path-based issuer format.
  • Appends your first configured audience to Okta’s authorization endpoint as an audience query parameter, because Okta does not support RFC 8707 resource indicators. You verify this in Step 3.
  • Proxies Dynamic Client Registration through the gateway, because Okta does not send CORS headers on its registration endpoint. Okta’s registration endpoint is relative to your org URL rather than the issuer, so agentgateway rewrites it to https://<your-org>.okta.com/oauth2/v1/clients.

Important

Unlike the other providers, Okta requires you to set jwks explicitly. Okta publishes its keys at {issuer}/v1/keys, but agentgateway derives {issuer}/.well-known/jwks.json for the okta provider, which Okta does not serve. If you omit jwks, token validation fails because agentgateway cannot fetch the signing keys.

For the underlying mcpAuthentication fields, see MCP authentication.

Before you begin

  1. Install the agentgateway binary.
  2. Install Node.js so that npx can run the sample MCP server.
  3. Make sure that you have access to an Okta org and permission to create an authorization server and app integrations in the Okta Admin Console. A free developer org is sufficient.

Step 1: Set up Okta

Create an authorization server and the app integrations that your clients use, then collect the values that agentgateway needs.

  1. In the Okta Admin Console, go to Security > API > Authorization Servers. Use the built-in default server, or add one for your MCP server. Note the Audience value on the server’s Settings tab.

  2. On the authorization server’s Scopes tab, add a scope that your MCP server enforces, such as agentgateway.

  3. On the authorization server’s Claims tab, add a groups claim so that a user’s group memberships appear in the access token. You use this claim in Step 5.

  4. Go to Applications > Applications and click Create App Integration. Select OIDC - OpenID Connect, then select Native Application for local MCP clients or Single-Page Application for browser-based clients. Both are public clients that use PKCE, which is what MCP clients require. Under Grant type, select Authorization Code and Refresh Token. Under Sign-in redirect URIs, add the callback URLs of the MCP clients that you plan to connect. Assign the app to the users or groups that need access, then click Save.

  5. Create a second app integration of type API Services, and assign it to a group named AI-Users. You use this application to request a token from the command line in Step 4, which keeps the verification steps scriptable. Note its Client ID and Client Secret.

  6. Save the values that the rest of this guide uses.

    export OKTA_ORG_URL='https://your-org.okta.com'
    export OKTA_ISSUER="${OKTA_ORG_URL}/oauth2/default"
    export OKTA_AUDIENCE='api://agentgateway'
    export OKTA_CLIENT_ID='<your-api-services-client-id>'
    export OKTA_CLIENT_SECRET='<your-api-services-client-secret>'
    VariableWhere to find it
    OKTA_ORG_URLYour Okta org URL, including https:// and with no trailing slash.
    OKTA_ISSUERThe authorization server URL. Replace default if you created your own server.
    OKTA_AUDIENCEThe Audience on the authorization server’s Settings tab.
    OKTA_CLIENT_ID and OKTA_CLIENT_SECRETThe General tab of the API Services application from step 5.

    Agentgateway expands ${...} references when it loads a configuration file, so the same variables also fill in the config.yaml that you create next. If a variable is unset, agentgateway exits with environment variable not found rather than starting with a broken configuration.

    Tip

    To confirm the issuer and the JWKS URL for your authorization server, open its metadata document at ${OKTA_ISSUER}/.well-known/openid-configuration and check the issuer and jwks_uri fields.

Step 2: Configure and start agentgateway

  1. Create a config.yaml file that exposes a sample MCP server on port 3000 and protects it with the okta provider.

    # yaml-language-server: $schema=https://agentgateway.dev/schema/config
    gateways:
      default:
        port: 3000
    routes:
    - backends:
      - mcp:
          targets:
          - name: everything
            stdio:
              cmd: npx
              args: ["@modelcontextprotocol/server-everything"]
      policies:
        mcpAuthentication:
          mode: strict
          issuer: ${OKTA_ISSUER}
          audiences:
          - ${OKTA_AUDIENCE}
          provider:
            okta: {}
          jwks:
            url: ${OKTA_ISSUER}/v1/keys
          resourceMetadata:
            resource: http://localhost:3000/mcp
            scopesSupported:
            - agentgateway
            bearerMethodsSupported:
            - header
    Review the following table to understand this configuration.
    SettingDescription
    issuerThe authorization server URL, with no trailing slash. This value must match the iss claim in the token.
    audiencesThe Audience of your authorization server. The first entry is the value that agentgateway sends to Okta as the audience query parameter, so list it first.
    provider.oktaEnables the Okta-specific behavior described in Why the Okta provider is needed. Takes no fields.
    jwks.urlRequired for Okta. Set it to {issuer}/v1/keys, because the URL that agentgateway derives for the okta provider is not a path that Okta serves.
    resourceMetadataThe protected resource metadata that agentgateway serves to MCP clients, which you inspect in Step 3.
  2. Start agentgateway.

    agentgateway -f config.yaml

    Example output:

    info  state_manager  loaded config from File("config.yaml")
    info  app            serving UI at http://localhost:15000/ui
    info  proxy::gateway started bind  bind="bind/3000"

Step 3: Verify that unauthenticated requests are rejected

Agentgateway runs in the foreground, so run the following commands in another terminal.

  1. Send an MCP initialize request without a token.

    curl -i -X POST http://localhost:3000/mcp \
      -H 'content-type: application/json' \
      -H 'accept: application/json, text/event-stream' \
      -d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}},"id":1}'

    Agentgateway returns 401 with a WWW-Authenticate header that points MCP clients at the protected resource metadata.

    HTTP/1.1 401 Unauthorized
    www-authenticate: Bearer resource_metadata="http://localhost:3000/.well-known/oauth-protected-resource/mcp"
    
    {"error":"unauthorized","error_description":"JWT token required"}
  2. Follow that pointer to see the metadata that the gateway serves.

    curl -s http://localhost:3000/.well-known/oauth-protected-resource/mcp

    Example output:

    {"resource":"http://localhost:3000/mcp","authorization_servers":["http://localhost:3000/mcp"],"mcp_protocol_version":"2025-06-18","resource_type":"mcp-server","bearer_methods_supported":["header"],"scopes_supported":["agentgateway"]}
  3. Confirm the two rewrites that the okta provider makes to the authorization server metadata.

    curl -s http://localhost:3000/.well-known/oauth-authorization-server

    The authorization_endpoint carries an audience query parameter that Okta’s own discovery document does not include, and the registration_endpoint points back at the gateway rather than at Okta.

    ...
    "authorization_endpoint": "https://your-org.okta.com/oauth2/default/v1/authorize?audience=api://agentgateway",
    "registration_endpoint": "http://localhost:3000/.well-known/oauth-authorization-server/client-registration",
  4. Register a client through that endpoint to confirm that the proxy works. Agentgateway forwards the request to your org-relative registration path, ${OKTA_ORG_URL}/oauth2/v1/clients.

    curl -s -X POST http://localhost:3000/.well-known/oauth-authorization-server/client-registration \
      -H 'content-type: application/json' \
      -d '{"client_name":"mcp-inspector","redirect_uris":["http://localhost:6274/oauth/callback"],"grant_types":["authorization_code"],"response_types":["code"],"token_endpoint_auth_method":"none"}'

    Okta returns the registered client, including a generated client_id.

    ...
    "client_id":"0oa1b2c3d4e5f6g7h8i9"

Step 4: Call the MCP server with a token

MCP clients complete the OAuth flow themselves. To get a token by hand, use the client credentials flow with the API Services application from Step 1.

  1. Request a token from your authorization server.

    export TOKEN="$(curl -s -X POST "${OKTA_ISSUER}/v1/token" \
      -H 'content-type: application/x-www-form-urlencoded' \
      -d grant_type=client_credentials \
      -d "client_id=${OKTA_CLIENT_ID}" \
      -d "client_secret=${OKTA_CLIENT_SECRET}" \
      -d "audience=${OKTA_AUDIENCE}" \
      -d scope=agentgateway \
      | jq -r .access_token)"
  2. Send the token as a bearer token.

    curl -i -X POST http://localhost:3000/mcp \
      -H "authorization: Bearer ${TOKEN}" \
      -H 'content-type: application/json' \
      -H 'accept: application/json, text/event-stream' \
      -d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}},"id":1}'

    Agentgateway fetches Okta’s keys from the jwks.url that you configured, validates the token, and returns the MCP server’s response.

    HTTP/1.1 200 OK
    content-type: text/event-stream
    mcp-session-id: 0511047b-3f97-4dcf-9fec-4457b4c3c229
    
    event: message
    data: {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05", ... ,"serverInfo":{"name":"mcp-servers/everything","title":"Everything Reference Server","version":"2.0.0"}}}

    Tip

    The client credentials flow is a convenience for this guide. Real MCP clients use the authorization code flow with PKCE, which the gateway advertises through the metadata that you inspected in Step 3.

Step 5: Restrict access by group

Because MCP authentication runs at the route level, you can use claims from the validated Okta token in an authorization policy. Okta includes a user’s group memberships in the groups claim when you add the groups claim to your authorization server, which you did in Step 1.

  1. Add an authorization policy alongside mcpAuthentication in your config.yaml that requires membership in the AI-Users group.

      policies:
        mcpAuthentication:
          mode: strict
          issuer: ${OKTA_ISSUER}
          audiences:
          - ${OKTA_AUDIENCE}
          provider:
            okta: {}
          jwks:
            url: ${OKTA_ISSUER}/v1/keys
          resourceMetadata:
            resource: http://localhost:3000/mcp
            scopesSupported:
            - agentgateway
            bearerMethodsSupported:
            - header
        authorization:
          rules:
          # Check for Okta group membership
          - '"AI-Users" in jwt.groups'
  2. Restart agentgateway to apply the policy. Because the API Services application is assigned to AI-Users, the request from Step 4 still succeeds.

    agentgateway -f config.yaml
  3. To confirm that the rule is enforced, create another API Services application that is not assigned to AI-Users. Save its credentials.

    export OKTA_UNAUTHORIZED_CLIENT_ID='<second-api-services-client-id>'
    export OKTA_UNAUTHORIZED_CLIENT_SECRET='<second-api-services-client-secret>'
  4. Request a token with that application and repeat the request.

    export NO_GROUP_TOKEN="$(curl -s -X POST "${OKTA_ISSUER}/v1/token" \
      -H 'content-type: application/x-www-form-urlencoded' \
      -d grant_type=client_credentials \
      -d "client_id=${OKTA_UNAUTHORIZED_CLIENT_ID}" \
      -d "client_secret=${OKTA_UNAUTHORIZED_CLIENT_SECRET}" \
      -d "audience=${OKTA_AUDIENCE}" \
      -d scope=agentgateway \
      | jq -r .access_token)"
    
    curl -s -o /dev/null -w '%{http_code}\n' -X POST http://localhost:3000/mcp \
      -H "authorization: Bearer ${NO_GROUP_TOKEN}" \
      -H 'content-type: application/json' \
      -H 'accept: application/json, text/event-stream' \
      -d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}},"id":1}'

    The token is valid, so authentication succeeds, but the authorization rule denies the request with 403.

    403

Connect an MCP client

Point your MCP client at the gateway’s MCP endpoint, http://localhost:3000/mcp. The client discovers the authorization server through the gateway, registers through the gateway-proxied registration endpoint that you verified in Step 3, and redirects the user to Okta to log in and consent.

Learn more

Was this page helpful?
Agentgateway assistant

Ask me anything about agentgateway configuration, features, or usage.

Note: AI-generated content might contain errors; please verify and test all returned information.

Tip: one topic per conversation gives the best results. Use the + button in the chat header to start a new conversation.

Switching topics? Starting a new conversation improves accuracy.
↑↓ navigate select esc dismiss

What could be improved?

Your feedback helps us improve assistant answers and identify docs gaps we should fix.

Need more help? Join us on Discord: https://discord.gg/y9efgEmppm

Want to use your own agent? Add the Solo MCP server to query our docs directly. Get started here: https://search.solo.io/.