Missing CORS headers for failed JWT validation

In Istio 1.6.13 we use JWT authentication via security.istio.io/v1beta1/RequestAuthentication and security.istio.io/v1beta1/AuthorizationPolicy attached to an Istio ingress gateway, and the corsPolicy feature in VirtualService s attached to that gateway to allow cross origin requests from our JavaScript based web app.

When JWT validation is successful, requests are successfully passed to our backend services, and responses include the proper CORS headers as defined in the VirtualServices. However, when JWT validation fails, responses do NOT include the proper CORS headers, which leads to browsers throwing CORS errors instead of returning the 40x responses.

I would expect JWT validation failures from RequestAuthentication and authorization failures from AuthorizationPolicy to still respond with CORS headers as defined in the corsPolicy of VirtualServices.

Here is output from curl while making a cross origin request to the Istio gateway WITH proper JWT token, notice that it does return expected CORS response headers:

$ curl -i \
  -H 'Origin: https://localhost' \
  -H 'Access-Control-Request-Method: POST,PUT' \
  -H 'Access-Control-Request-Headers: foo' \
  https://a.example.com \
  -H 'Authorization: Bearer <REDACTED valid token>'

HTTP/2 200
access-control-allow-origin: https://localhost
access-control-allow-credentials: true
access-control-allow-methods: GET,HEAD,OPTIONS
access-control-allow-headers: foo
access-control-max-age: 1800
date: Mon, 04 Jan 2021 18:55:17 GMT
server: istio-envoy

Compared to cross origin request to the gateway WITHOUT proper JWT token, notice that it DOES NOT return expected CORS response headers:

$ curl -i \
  -H 'Origin: https://localhost' \
  -H 'Access-Control-Request-Method: POST,PUT' \
  -H 'Access-Control-Request-Headers: foo' \
  https://a.example.com \
  -H 'Authorization: Bearer <REDACTED expired token>'


HTTP/2 401
content-length: 14
content-type: text/plain
date: Mon, 04 Jan 2021 19:00:17 GMT
server: istio-envoy

Jwt is expired

I am using a RequestAuthentication resource, specifying JWT authentication for the gateway similar to below:

apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
  name: foo
  namespace: istio-system
spec:
  jwtRules:
  - forwardOriginalToken: true
    fromHeaders:
    - name: Authorization
      prefix: 'Bearer '
    issuer: https://your.jwt.issuer
    jwksUri: https://your.jwks.endpoint
  selector:
    matchLabels:
      app: foo

An AuthorizationPolicy resource, disallowing non-authenticated requests to the gateway similar to below:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: foo
  namespace: istio-system
spec:
  action: ALLOW
  rules:
  - to:
    - operation:
        paths:
        - /*
    when:
    - key: request.auth.claims[iss]
      values:
      - https://your.jwt.issuer
  selector:
    matchLabels:
      app: foo

Finally, our VirtualService resource exposing your service behind the gateway with a corsPolicy is similar to:

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  labels:
    app: bar
  name: bar
  namespace: default
spec:
  gateways:
  - istio-system/foo
  hosts:
  - a.example.com
  http:
  - corsPolicy:
      allowCredentials: true
      allowHeaders:
      - foo
      allowMethods:
      - GET
      - HEAD
      - OPTIONS
      allowOrigin:
      - https://localhost
      allowOrigins:
      - exact: https://localhost
      maxAge: 30m
    route:
    - destination:
        host: bar.default.svc.cluster.local

Is it expected in Istio 1.6.13 to be missing the CORS headers for all failed JWT validations? We hate to remove JWT validation from the gateway and need to handle it in the upstream app but don’t see a way to do so in Istio.

linking the issue that must have been created CORS error for failed JWT validation · Issue #29425 · istio/istio · GitHub