Skip to content

Unable to authenticate traffic to a specific route through security policy when a general gateway-level security policy exists #8649

@kyfrankie

Description

@kyfrankie

Description:

I am using envoy gateway in namespace mode and I have various frontends and backends at different subdomains which the single gateway handles traffic across these subdomains.

I have two security policy - policy a at gateway level and policy b targeting a specific route to a particular backend. Both policies share the same oidc and jwt configurations but policy b has an additional authorisation configuration to perform additional check against jwt claims.

When policy b is present, the frontend is unable to send request to that particular backend with 302 Found (envoy log: oauth missing credentials). When trying to open the backend url on the browser directly, 401 error with oauth flow error message was shown and envoy logs showing CSRF token validation failed. Even after modifying policy b to be exactly the same as policy a, same error exists. After policy b is deleted, the frontend can successfully send request to the protected backend with no issue.

This behaviour appears related to, but not fully explained by, this issue: #7205. The symptoms are similar (unexpected auth redirects and CSRF-related failures), but the setup differs due to the shared OIDC/JWT sessions and the additional authorization check in a route-specific policy.

Repro steps:

policy a:

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
  name: gateway-security-policy
  namespace: {{ .Values.namespace }}
spec:
  targetRefs:
    - group: gateway.networking.k8s.io
      kind: Gateway
      name: internal-gateway
  cors:
    allowOrigins:
      {{- range .Values.instances }}
      - "https://{{ .subdomain }}.{{ $.Values.namespace }}.{{ $.Values.domain }}"
      {{- end }}
    allowMethods: 
      - GET
      - POST
      - PUT
      - DELETE
      - OPTIONS
    allowHeaders:
      - Accept
      - Authorization
      - Content-Type
      - X-CSRF-Token
    exposeHeaders:
      - Link
    allowCredentials: true
  oidc:
    provider:
      issuer: https://auth.{{ .Values.domain }}
    clientIDRef:
      name: internal-gateway-secret
      namespace: {{ .Values.namespace }}
    clientSecret:
      name: internal-gateway-secret
      namespace: {{ .Values.namespace }}
    redirectURL: https://auth.{{ .Values.namespace }}.{{ .Values.domain }}/oauth2/callback
    scopes:
        - openid
        - profile
        - offline_access
        - urn:zitadel:iam:org:project:{{ .Values.gateway.project_id }}:roles
    logoutPath: /logout
    cookieDomain: "{{ .Values.namespace }}.{{ .Values.domain }}"
    forwardAccessToken: true
  jwt:
    providers:
    - name: zitadel
      remoteJWKS:
        uri: https://auth.{{ .Values.domain }}/oauth/v2/keys
      claimToHeaders:
      - claim: sub
        header: X-Zitadel-Sub
      - claim: urn:zitadel:iam:org:project:{{ .Values.gateway.project_id }}:roles
        header: X-Zitadel-Roles

policy b (extra config):

targetRefs:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      name: xxx
authorization:
    defaultAction: Deny
    rules:
      - action: Allow
        name: allow-user
        principal:
          jwt:
            claims:
              - name: sub
                valueType: StringArray
                values:
                  - 'xxx'
            provider: zitadel
      - action: Allow
        name: allow-roles
        principal:
          jwt:
            claims:
              - name: urn:zitadel:iam:org:project:xxx:roles
                valueType: StringArray
                values:
                  - admin
            provider: zitadel

Environment:

gateway-helm 1.7.1

Logs:

302 Found (oauth missing credentials)

[pod/internal-gateway-65b697df8b-r8g2m/envoy] {":authority":","bytes_received":0,"bytes_sent":0,"connection_termination_details":null,"downstream_local_address":"10.244.2.217:10080","downstream_remote_address":"10.226.0.6:20089","duration":0,"method":"GET","protocol":"HTTP/1.1","requested_server_name":null,"response_code":302,"response_code_details":"oauth.missing_credentials","response_flags":"-","route_name":"i","start_time":"2026-04-01T14:37:09.644Z","upstream_cluster":"httproute//rule/0","upstream_host":null,"upstream_local_address":null,"upstream_transport_failure_reason":null,"user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0","x-envoy-origin-path":"/","x-envoy-upstream-service-time":null,"x-forwarded-for":"","x-request-id":"f2a86c40-f883-4991-b795-dd9fce2ec3cb"}

401 unauthorised (CSRF token validation failed)

[pod/internal-gateway-65b697df8b-r8g2m/envoy] [2026-04-01 14:38:40.746][14][warning][oauth2] [source/extensions/filters/http/oauth2/filter.cc:1390] [Tags: "ConnectionId":"141","StreamId":"4191718425724896979"] Responding with 401 Unauthorized. Cause: CSRF token validation failed

[pod/internal-gateway-65b697df8b-r8g2m/envoy] {":authority":"","bytes_received":0,"bytes_sent":18,"connection_termination_details":null,"downstream_local_address":"10.244.2.217:10080","downstream_remote_address":"10.226.0.6:44793","duration":0,"method":"GET","protocol":"HTTP/1.1","requested_server_name":null,"response_code":401,"response_code_details":"CSRF_token_validation_failed","response_flags":"-","route_name":"httproute//rule/0/match/0/","start_time":"2026-04-01T14:38:40.746Z","upstream_cluster":null,"upstream_host":null,"upstream_local_address":null,"upstream_transport_failure_reason":null,"user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0","x-envoy-origin-path":"/oauth2/callback?code=some-code","x-envoy-upstream-service-time":null,"x-forwarded-for":"","x-request-id":"e9d381a9-52c0-44f6-85a3-c3d54cc034ac"}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions