istio/istio · error

JWT claim based routing (key: %s) is only supported for gate

Error message

JWT claim based routing (key: %s) is only supported for gateway, found no gateways: %v

What it means

Istio only supports JWT claim based routing (header match keys recognized by jwt.ToRoutingClaim, e.g. 'request.auth.claims.foo' or 'request.audiences') on routes bound to a gateway. This error fires when a VirtualService that applies only to the mesh (no gateway, or gateways resolving to mesh semantics) uses such a claim key in match.headers or match.withoutHeaders.

Source

Thrown at pkg/config/validation/validation.go:1782

		}
		gatewaySemantics := cfg.Annotations[constants.InternalRouteSemantics] == constants.RouteSemanticsGateway

		appliesToMesh := false
		appliesToGateway := false
		if len(virtualService.Gateways) == 0 {
			appliesToMesh = true
		} else {
			errs = AppendValidation(errs, validateGatewayNames(virtualService.Gateways, gatewaySemantics))
			appliesToGateway = isGateway(virtualService)
			appliesToMesh = !appliesToGateway
		}

		if !appliesToGateway {
			validateJWTClaimRoute := func(headers map[string]*networking.StringMatch) {
				for key := range headers {
					if jwt.ToRoutingClaim(key).Match {
						msg := fmt.Sprintf("JWT claim based routing (key: %s) is only supported for gateway, found no gateways: %v", key, virtualService.Gateways)
						errs = AppendValidation(errs, errors.New(msg))
					}
				}
			}
			for _, http := range virtualService.GetHttp() {
				for _, m := range http.GetMatch() {
					validateJWTClaimRoute(m.GetHeaders())
					validateJWTClaimRoute(m.GetWithoutHeaders())
				}
			}
		}

		allHostsValid := true
		for _, virtualHost := range virtualService.Hosts {
			var err error
			if appliesToGateway {
				err = agent.ValidateWildcardDomainForVirtualServiceBoundToGateway(isSniHost(virtualService), virtualHost)
			} else {
				err = agent.ValidateWildcardDomain(virtualHost)

View on GitHub (pinned to 8dc789c5cf)

Solutions

  1. Attach the VirtualService to a real gateway, e.g. gateways: ["my-gateway-ns/my-gateway"]
  2. Remove the JWT claim match keys from headers/withoutHeaders for mesh-internal routing and use plain header or destination-based matching instead
  3. Verify gateway names with istioctl proxy-config or kubectl get gateway

Example fix

# before
kind: VirtualService
spec:
  gateways: ["mesh"]
  http:
    - match:
        - headers:
            request.auth.claims/roles: { exact: "admin" }
# after
kind: VirtualService
spec:
  gateways: ["istio-system/my-gateway"]
  hosts: ["api.example.com"]
  http:
    - match:
        - headers:
            request.auth.claims/roles: { exact: "admin" }
Defensive patterns

Strategy: validation

Validate before calling

func usesJWTClaimKey(h map[string]*StringMatch) bool {
	for k := range h {
		if strings.HasPrefix(k, "request.auth.claims/") || strings.HasPrefix(k, "request.audiences") || strings.HasPrefix(k, "request.auth.presenter") || strings.HasPrefix(k, "request.auth.principal") {
			return true
		}
	}
	return false
}

func checkClaimRoutingNeedsGateway(vs *VirtualService) error {
	for _, h := range vs.HTTP {
		for _, m := range h.Match {
			if (usesJWTClaimKey(m.Headers) || usesJWTClaimKey(m.WithoutHeaders)) && len(vs.Gateways) == 0 {
				return errors.New("JWT claim routing requires a gateway; add spec.gateways")
			}
		}
	}
	return nil
}

Prevention

When it happens

Trigger: A VirtualService with gateways: ["mesh"] (or no gateways at all) whose HTTPMatchRequest headers/withoutHeaders map contains a JWT routing claim key like request.auth.claims/roles.

Common situations: Reusing a gateway-oriented VirtualService in a sidecar/mesh-only setup; explicitly setting gateways to ["mesh"] while keeping claim-based match keys; assuming claim routing works for east-west traffic.

Related errors


AI-assisted analysis of istio/istio@8dc789c5cf (2026-08-15). Data as JSON: /api/errors/3649783423c6572f. Report an issue: GitHub.