Configure authentication and credential injection
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
GatewayClassnamedmcpgwclasscreated and bound to the NetScaler instance. -
An MCP Gateway (
mcp-gw, namespacedefault) with an HTTPS listener, and one or moremcprouteresources that route by thex-netscaler-target-mcp-serverheader. -
For OAuth 2.1 client authentication: an external NetScaler VPX/MPX/BLX (
GatewayClasscontrollerName: 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_urlto 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_urlandclient_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 exampleRS256. -
user_field— the token claim used as the user identity, for examplesub.
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.