Configure authentication and credential injection

Last published : Nov 02, 2026
The NetScaler® MCP Gateway governs credentials on both sides of a connection: it can require callers to present a valid OAuth 2.1 bearer token before a request reaches any MCP server, and it can supply the upstream credential the backend MCP server expects. This page describes the three credential models the Gateway supports and how to configure each.
Credential model What it does Platform
OAuth 2.1 client authentication The Gateway challenges clients (401 with WWW-Authenticate) and validates bearer tokens against an external identity provider (IdP) NetScaler VPX/MPX/BLX only
Gateway credential injection The Gateway injects an upstream credential (bearer token or API key) from a Kubernetes Secret toward the backend MCP server NetScaler CPX and VPX/MPX/BLX
Workload-held credentials The client or workload holds its own credential and the Gateway passes it through unchanged NetScaler CPX and VPX/MPX/BLX

Prerequisites

  • NetScaler Kubernetes Gateway Controller deployed with the desired configuration.
  • A GatewayClass named mcpgwclass created and bound to the NetScaler instance.
  • An MCP Gateway (mcp-gw, namespace default) with an HTTPS listener, and one or more mcproute resources that route by the x-netscaler-target-mcp-server header.
  • For OAuth 2.1 client authentication: an external NetScaler VPX/MPX/BLX (GatewayClass controllerName: citrix.com/nsgw-controller) and an OAuth 2.1 IdP. For local token validation the IdP must publish a JWKS endpoint; for token introspection it must expose an RFC 7662 introspection endpoint.

Configuration

OAuth 2.1 client authentication (VPX/MPX/BLX only)

When a route carries an authpolicy filter, the Gateway challenges every unauthenticated request with 401 Unauthorized and a WWW-Authenticate: Bearer header that points the client at the IdP. The client obtains an OAuth 2.1 access token from the IdP and re-sends the request with an Authorization: Bearer <token> header. The Gateway validates the token before the request reaches the MCP server.
The authpolicy is a NetScaler CRD referenced from an mcproute through an ExtensionRef filter. The same policy can be shared by many routes.
Note:
OAuth 2.1 client authentication requires NetScaler VPX/MPX/BLX; it is not available for MCP on NetScaler CPX.
The Gateway can validate tokens two ways, depending on your IdP:
  • Local JWT validation (JWKS) — the Gateway verifies the token signature against the IdP public keys published at a jwks_uri. No round-trip to the IdP per request and no Secret on the Gateway. Use this for IdPs that issue signed JWTs, such as Auth0.
  • Token introspection (RFC 7662) — the Gateway calls the IdP introspect_url to validate each token, authenticating with a client ID and secret stored in a Kubernetes Secret. Use this for IdPs that expose an introspection endpoint, such as Okta.

authpolicy for Auth0 (JWKS)

This authpolicy validates the bearer token locally against the Auth0 tenant public keys. No Secret is required on the Gateway.
kubectl apply -f - <<EOF
apiVersion: citrix.com/v1beta1
kind: authpolicy
metadata:
  name: mcp-client-auth
  namespace: default
spec:
  authentication_mechanism:
    # 401-challenge flow: the Gateway reads the bearer token from the request header.
    using_request_header: 'ON'
    authentication_host_cert:
      # Server certificate for the authentication virtual server. Reuse the Gateway
      # listener TLS Secret so the front door and the auth vserver share one certificate.
      tls_secret: mcp-gateway-tls
  authentication_providers:
    - name: auth0
      oauth:
        # issuer must match the token iss claim, including the trailing slash.
        issuer: "https://<tenant>.us.auth0.com/"
        # Local JWT validation against the IdP public keys. No Secret needed in JWKS mode.
        jwks_uri: "https://<tenant>.us.auth0.com/.well-known/jwks.json"
        audience:
          - "https://mcp-gateway/api"
        signature_algorithms: [RS256]
        user_field: sub
  authentication_policies:
    - expression: "true"        # authenticate every request that reaches a tagged route
      provider: ["auth0"]
  authorization_policies:
    - expression: "true"        # allow any authenticated caller (tighten with a claim or path rule)
EOF

authpolicy for Okta (token introspection)

This variant validates each token by calling the Okta introspection endpoint. Create a Secret holding the Okta application client ID and secret first, then reference it from the policy.
kubectl create secret generic mcp-okta-client \
  --from-literal=client_id=<client-id> \
  --from-literal=client_secret=<client-secret>
kubectl apply -f - <<EOF
apiVersion: citrix.com/v1beta1
kind: authpolicy
metadata:
  name: mcp-client-auth
  namespace: default
spec:
  authentication_mechanism:
    using_request_header: 'ON'
    authentication_host_cert:
      # A server certificate preconfigured on the appliance.
      preconfigured: ns-server-certificate
  authentication_providers:
    - name: okta
      oauth:
        issuer: "https://<your-okta-domain>/oauth2/default"
        introspect_url: "https://<your-okta-domain>/oauth2/default/v1/introspect"
        client_credentials: mcp-okta-client   # Secret with client_id and client_secret
        user_field: sub
  authentication_policies:
    - expression: "true"
      provider: ["okta"]
  authorization_policies:
    - expression: "true"
EOF
The oauth block takes the following provider fields:
  • issuer — the token issuer (iss) the Gateway accepts. It must match the token exactly, including any trailing slash.
  • jwks_uri — the IdP JWKS endpoint used for local JWT signature validation (JWKS mode).
  • introspect_url and client_credentials — the RFC 7662 introspection endpoint and the Kubernetes Secret that holds the client ID and secret (introspection mode).
  • audience — the audience (aud) the token must carry.
  • signature_algorithms — the JWT signature algorithms the Gateway accepts, for example RS256.
  • user_field — the token claim used as the user identity, for example sub.
Both examples use the same CR name (mcp-client-auth), so a route binds either IdP without change. Apply one policy or the other, not both.
Note:
Create the authpolicy without service names so each route can attach it through its own ExtensionRef filter.
Important:
Authentication and authorization are separate gates. After a token is validated, NetScaler runs authorization, and the appliance default is DENY — a request with a valid token but no matching allow-policy receives 403 Forbidden, not 200. The authorization_policies entry above becomes an ALLOW policy bound to the route's load balancing virtual server, so an authenticated caller is permitted. A route that omits the authpolicy filter is open (no token required).

mcproute that references the authpolicy

Attach the policy to a route with one ExtensionRef filter of kind authpolicy. The route below also references its backend profile (see Gateway credential injection).
kubectl apply -f - <<EOF
apiVersion: citrix.com/v1
kind: mcproute
metadata:
  name: mcp-github
  namespace: default
spec:
  parentRefs:
    - group: gateway.networking.k8s.io
      kind: Gateway
      name: mcp-gw
      namespace: default
  rules:
    - matches:
        - headers:
            - name: x-netscaler-target-mcp-server
              type: MCPTargetServer
              value: github.mcp.example
      filters:
        - type: ExtensionRef
          extensionRef:
            group: citrix.com
            kind: mcpprofile
            name: mcp-github-backend
        - type: ExtensionRef
          extensionRef:
            group: citrix.com
            kind: authpolicy
            name: mcp-client-auth
      backendRefs:
        - name: mcp-github
          port: 443
EOF
Note:
The routing header value in the mcproute (github.mcp.example) must appear in the host portion of the x-netscaler-target-mcp-server header, that is, the part before the first slash. NetScaler checks whether that part contains the value (a substring match, not a prefix match). Clients send the header with a trailing slash (github.mcp.example/). The trailing slash is required on the client request and is described in the routing configuration.

Gateway credential injection

The Gateway can supply the upstream credential the backend MCP server expects, so the client never holds it. The credential lives in a Kubernetes Secret referenced by a backend mcpprofile. When insertHeaderInClientRequest is ENABLED and mcpTokenOrApi names a Secret, the Gateway injects the stored credential on every request it forwards to the backend.
This works on both NetScaler CPX and VPX/MPX/BLX. On VPX/MPX/BLX you can combine it with OAuth 2.1 client authentication: the Gateway validates the client token at the front door, then injects the upstream credential toward the backend.
Create the Secret holding the upstream credential. The value is the complete Authorization header line the Gateway injects, and the key defaults to token.
kubectl create secret generic mcp-github-token \
  --from-literal=token='Authorization: Bearer <token>'
Create the backend mcpprofile that references the Secret.
kubectl apply -f - <<EOF
apiVersion: citrix.com/v1
kind: mcpprofile
metadata:
  name: mcp-github-backend
  namespace: default
spec:
  config:
    profileType: BACKEND
    proxyMode: FORWARD
    hostReplacement: ENABLED               # set the upstream Host to the backend server
    urlReplacement: DISABLED               # the server serves MCP at /mcp -> preserve the path
    insertHeaderInClientRequest: ENABLED   # inject the stored credential upstream
    mcpTokenOrApi:
      name: mcp-github-token               # Secret holding the upstream credential (key: token)
    comment: "github backend - gateway-held credential injection"
EOF
The mcp-github route from the previous section references this profile through its mcpprofile ExtensionRef filter.
Note:
The Secret referenced by mcpTokenOrApi must be in the same namespace as the mcpprofile. Cross-namespace Secret references are rejected. To reference a Secret key other than token, add a key field under mcpTokenOrApi.
Note:
On NetScaler CPX, use the same mcpprofile, but the route references only the mcpprofile filter and omits the authpolicy filter (OAuth 2.1 client authentication is not available on CPX).

Workload-held credentials

In this model the Gateway injects nothing. The client or the backend workload holds the credential, and the Gateway passes the request through unchanged. Set insertHeaderInClientRequest to DISABLED on the backend mcpprofile.
kubectl apply -f - <<EOF
apiVersion: citrix.com/v1
kind: mcpprofile
metadata:
  name: mcp-atlassian-backend
  namespace: default
spec:
  config:
    profileType: BACKEND
    proxyMode: FORWARD
    hostReplacement: ENABLED
    urlReplacement: DISABLED
    insertHeaderInClientRequest: DISABLED   # the Gateway injects nothing; the credential passes through
    comment: "atlassian backend - workload holds its own credential"
EOF
For example, an Atlassian (Jira/Confluence) MCP server holds its own API credentials, typically supplied to the pod through an envFrom Secret. The Gateway routes to it and adds no upstream credential. This works on both NetScaler CPX and VPX/MPX/BLX.
Note:
If you also enable OAuth 2.1 client authentication on the route (VPX/MPX/BLX), the Gateway's AAA path consumes and strips the client Authorization header after validating it, so the backend does not receive the client token. In that case the backend workload must supply its own credential.

Reference