Skip to content

For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.

Basic JWT policy

Page as Markdown

Enforce JWT authentication with a GatewayExtension and TrafficPolicy.

Use JWT authentication to verify that incoming requests carry a token issued by a trusted provider before allowing them to reach your upstream services. This configuration lets you protect your APIs from unauthenticated access without adding authentication logic to each service. Enforce JWT authentication by creating a GatewayExtension with a JWT provider and referencing it from a TrafficPolicy.

Before you begin

  1. Follow the Get started guide to install kgateway.

  2. Follow the Sample app guide to create a gateway proxy with an HTTP listener and deploy the httpbin sample app.

  3. Get the external address of the gateway and save it in an environment variable.

    export INGRESS_GW_ADDRESS=$(kubectl get svc -n kgateway-system http -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}")
    echo $INGRESS_GW_ADDRESS  

Set up JWT authentication

  1. Create a GatewayExtension resource with a jwt configuration. The GatewayExtension holds one or more JWT provider definitions, including the issuer and JWKS source that you want to use to validate incoming tokens. By keeping the provider configuration in a separate resource, the same GatewayExtension can be referenced from more than one TrafficPolicy.

    kubectl apply -f- <<EOF
    apiVersion: gateway.kgateway.dev/v1alpha1
    kind: GatewayExtension
    metadata:
      name: selfminted-jwt
      namespace: kgateway-system
    spec:
      jwt:
        providers:
          - name: selfminted
            issuer: kgateway.dev
            jwks:
              local:
                inline: '{"keys":[{"kty":"RSA","kid":"kgateway-public-key-001","use":"sig","alg":"RS256","n":"tNxnW0ZghyIUdfRc97EuZ6Hii0z4AucJrbOCT8MxKznlnV9Z-OrOYMf_hyjiD2Q_qyGrv-sRhinKOjokr-cbLKhHlAlEkEW1ah4wQ-zzO3DT0SdAKX_7RkMkl5Sba443vfDlDmuVSBeyHQr6cKZZGBIe8TlzcKR0xYlop13p1DYAHsIiX8A_q2CmsRlnV4CbneNMGZOmHuBiFG3DJ2lc1ZgvKc8SN1gt3oEujRqxy4yPLHVJ3wQ58ezYtgV2gzbyllzJdi1DSoPtnCFFGvfDqmAcDdmfVtHUHqagCF0ivEQsrxt7PYKqxuCbkaSY1_ef7ub01_5KF1GhlA9y5XSqJQ","e":"AQAB"}]}'
    EOF
    FieldDescription
    spec.jwt.providersA list of JWT providers. If multiple providers are listed, a token that validates against any one of them is accepted (OR logic).
    nameAn arbitrary name for this provider entry.
    issuerThe expected value of the iss claim. Tokens with a different issuer are rejected. If omitted, the iss field is not checked.
    jwks.local.inlineAn inline JSON Web Key Set (JWKS) used to verify token signatures. To fetch the keys from a remote JWKS server instead, see Remote JWKS.
  2. Create the TrafficPolicy resource that points to the GatewayExtension that you created in the previous step. The following policy applies JWT authentication to all routes on the Gateway. Create the policy in the same namespace as the targeted resource.

    kubectl apply -f- <<EOF
    apiVersion: gateway.kgateway.dev/v1alpha1
    kind: TrafficPolicy
    metadata:
      name: jwt-policy
      namespace: kgateway-system
    spec:
      targetRefs:
        - group: gateway.networking.k8s.io
          kind: Gateway
          name: http
      jwtAuth:
        extensionRef:
          name: selfminted-jwt
    EOF
    FieldDescription
    targetRefsThe resource to enforce the policy on. When targeting a Gateway, the policy must be in the same namespace as the Gateway. To restrict JWT enforcement to a single route instead, change the kind field to HTTPRoute and provide the name of the HTTPRoute resource that defines the routes you want to protect. Make sure to create the policy in the same namespace as the HTTPRoute that you target.
    jwtAuth.extensionRefThe name of the GatewayExtension resource that holds the JWT provider configuration. The extension must be in the same namespace as the policy.
  3. Send a request without a JWT and verify that you get a 401 Unauthorized response.

    curl -vik http://$INGRESS_GW_ADDRESS:8080/headers -H "host: www.example.com:8080"

    Example output:

    < HTTP/1.1 401 Unauthorized
    Jwt is missing

    Warning

    Got a 200 OK instead? The controller silently ignores TrafficPolicy resources that target a resource in a different namespace. Verify that both resources were created in the correct namespace and that the controller accepted them.

    kubectl get TrafficPolicy jwt-policy -n kgateway-system -o yaml | grep -A10 status
    kubectl get gatewayextension selfminted-jwt -n kgateway-system -o yaml | grep -A10 status

    Both resources must show an Accepted condition. If either has no status at all, the resource could be in the wrong namespace.

  4. Save a sample JWT token and send it in the Authorization header. The token is signed by the same issuer and key that you configured in the GatewayExtension resource and can be successfully validated by the gateway proxy.

    export TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtnYXRld2F5LXB1YmxpYy1rZXktMDAxIn0.eyJpc3MiOiJrZ2F0ZXdheS5kZXYiLCJvcmciOiJrZ2F0ZXdheS5kZXYiLCJzdWIiOiJhbGljZSIsInRlYW0iOiJkZXYiLCJleHAiOjIwNzQyNzQ4ODQsImxsbXMiOnsib3BlbmFpIjpbImdwdC0zLjUtdHVyYm8iXX19.YCxMm0TmecXsbcbNp6_GXlq5hCFGMD7KhLdOrp3EqzOKl_NX5vm6sNCMNSq5LjbCSKGThn66fnI4P6rlXke7w5kj8khIXQwDn7R0Dy5QOpLAFyE7pk8QGAjkgEGu37bxht5VjbsORdmrfxep1MTy3UEqef60Zwxwt3UtG5KmnsyyedmsCeodPNiNfuhA43r4KahpYg9cIMAnU_Wg-52ztwtqbrVRGxmoj6Efply4FE0xSKhKJZhulViriXR5K2y4zSdxenKvprO46u2ZSka7nq9ehpw_Oqhcwezw7So3lV_xpohiFz_-PGX97TXR1zi0ATjjp7VFxhkbggk8nEEFkQ
    curl -vik http://$INGRESS_GW_ADDRESS:8080/headers \
      -H "host: www.example.com:8080" \
      --header "Authorization: Bearer $TOKEN"

    Verify that you get a 200 OK response.

Forward JWT claims as request headers

You can extract claims from the verified JWT and forward them as headers to the upstream service by using the claimsToHeaders field in the GatewayExtension resource.

  1. Update the GatewayExtension resource to define the claims that you want to add as headers to the request before it is forwarded upstream. The following example extracts the team and org claims from the verified JWT and forwards them to the upstream service as the x-team and x-org headers.

    kubectl apply -f- <<EOF
    apiVersion: gateway.kgateway.dev/v1alpha1
    kind: GatewayExtension
    metadata:
      name: selfminted-jwt
      namespace: kgateway-system
    spec:
      jwt:
        providers:
          - name: selfminted
            issuer: kgateway.dev
            claimsToHeaders:
              - name: team
                header: x-team
              - name: org
                header: x-org
            jwks:
              local:
                inline: '{"keys":[{"kty":"RSA","kid":"kgateway-public-key-001","use":"sig","alg":"RS256","n":"tNxnW0ZghyIUdfRc97EuZ6Hii0z4AucJrbOCT8MxKznlnV9Z-OrOYMf_hyjiD2Q_qyGrv-sRhinKOjokr-cbLKhHlAlEkEW1ah4wQ-zzO3DT0SdAKX_7RkMkl5Sba443vfDlDmuVSBeyHQr6cKZZGBIe8TlzcKR0xYlop13p1DYAHsIiX8A_q2CmsRlnV4CbneNMGZOmHuBiFG3DJ2lc1ZgvKc8SN1gt3oEujRqxy4yPLHVJ3wQ58ezYtgV2gzbyllzJdi1DSoPtnCFFGvfDqmAcDdmfVtHUHqagCF0ivEQsrxt7PYKqxuCbkaSY1_ef7ub01_5KF1GhlA9y5XSqJQ","e":"AQAB"}]}'
    EOF
    FieldDescription
    claimsToHeadersA list of JWT claims to extract and forward as request headers to the upstream service.
    nameThe name of the JWT claim to extract, for example team.
    headerThe HTTP header name to set with the extracted claim value, for example x-team.
  2. Send the request again with the JWT. Verify that the response includes the X-Team and X-Org headers, which the gateway extracted from the token’s team and org claims and forwarded to the upstream service.

    curl -vik http://$INGRESS_GW_ADDRESS:8080/headers \
      -H "host: www.example.com:8080" \
      --header "Authorization: Bearer $TOKEN"

    Example output:

    {
      "headers": {
        ...
        "X-Org": [
          "kgateway.dev"
        ],
        "X-Team": [
          "dev"
        ]
      }
    }

Other configurations

Review other common JWT configuration examples.

Remote JWKS

Instead of embedding the JWKS keys inline, you can point the provider at a remote JWKS server, such as the JWKS endpoint of an external identity provider. The gateway fetches the keys from the server and caches them, which means you do not have to update the GatewayExtension when the provider rotates its keys.

The following example uses Keycloak as the identity provider.

  1. Set the following environment variables to match your Keycloak installation.

    export HOST_KEYCLOAK=<keycloak-host>    # hostname of your Keycloak server, for example keycloak.example.com
    export PORT_KEYCLOAK=443               # port that Keycloak listens on
    export KEYCLOAK_URL=https://$HOST_KEYCLOAK
  2. Create a Backend resource that points to the Keycloak host.

    kubectl apply -f- <<EOF
    apiVersion: gateway.kgateway.dev/v1alpha1
    kind: Backend
    metadata:
      name: keycloak
      namespace: kgateway-system
    spec:
      type: Static
      static:
        hosts:
          - host: $HOST_KEYCLOAK
            port: $PORT_KEYCLOAK
    EOF
  3. Update the GatewayExtension to use the Keycloak JWKS endpoint. Set the issuer to the Keycloak realm URL and the url to the realm’s JWKS endpoint. The backendRef points to the Backend that you created in the previous step. The kgateway proxy uses this Backend to fetch the JWKS keys from Keycloak.

    kubectl apply -f- <<EOF
    apiVersion: gateway.kgateway.dev/v1alpha1
    kind: GatewayExtension
    metadata:
      name: selfminted-jwt
      namespace: kgateway-system
    spec:
      jwt:
        providers:
          - name: keycloak
            issuer: $KEYCLOAK_URL/realms/master
            jwks:
              remote:
                url: $KEYCLOAK_URL/realms/master/protocol/openid-connect/certs
                backendRef:
                  name: keycloak
                  kind: Backend
                  group: gateway.kgateway.dev
                cacheDuration: 10m
    EOF
    FieldDescription
    jwks.remote.urlThe full URL of the JWKS endpoint, including protocol, host, and path.
    jwks.remote.backendRefA reference to the Backend that fronts the JWKS server. The kgateway proxy uses this Backend to fetch the JWKS keys from Keycloak. Set kind to Backend and group to gateway.kgateway.dev.
    jwks.remote.cacheDurationHow long the gateway caches the fetched keys before it refreshes them. If omitted, the keys are cached for 5 minutes.

    For more information, see the API docs.

Async JWKS fetch

By default, the gateway fetches the JWKS synchronously during request handling. If the JWKS server is slow or temporarily unavailable, JWT validation can fail. You can configure the gateway proxy to fetch and cache the JWKS asynchronously in the background instead by using the asyncFetch field.

jwks:
  remote:
    url: $KEYCLOAK_URL/realms/master/protocol/openid-connect/certs
    backendRef:
      name: keycloak
      kind: Backend
      group: gateway.kgateway.dev
    asyncFetch:
      fastListener: true
      failedRefetchDuration: 10s
FieldDescription
asyncFetch.fastListenerIf true, the listener starts serving traffic immediately without waiting for the first JWKS fetch to complete. If false or unset, the listener waits for the initial fetch before accepting requests, so tokens are never validated against an empty key set.
asyncFetch.failedRefetchDurationHow long to wait before retrying after a failed JWKS fetch. Accepts Go duration strings such as 500ms or 10s. If unset, Envoy defaults to 1s.

JWKS retry policy

You can configure exponential backoff retries when the JWKS server is unavailable by using the retryPolicy field.

jwks:
  remote:
    url: $KEYCLOAK_URL/realms/master/protocol/openid-connect/certs
    backendRef:
      name: keycloak
      kind: Backend
      group: gateway.kgateway.dev
    retryPolicy:
      numRetries: 3
      backOff:
        baseInterval: 1s
        maxInterval: 30s
FieldDescription
retryPolicy.numRetriesNumber of retry attempts when the JWKS fetch fails. Must be at least 1. Defaults to 1 if unset.
retryPolicy.backOff.baseIntervalThe initial backoff interval. Required. Accepts Go duration strings, such as 500ms or 1s.
retryPolicy.backOff.maxIntervalThe maximum backoff interval. Optional. Must be greater than or equal to baseInterval. If unset, Envoy defaults to 10 times the baseInterval.

In most cases, you do not need to configure a retryPolicy or asyncFetch policy. The defaults are intended to work for typical JWKS endpoints. Use the following guidelines to choose appropriate values.

ScenarioRecommended configuration
JWKS endpoint is external or occasionally slowIncrease numRetries (e.g., 3-5) and use a larger backOff.maxInterval (e.g., 30s-60s) to handle intermittent failures.
Gateway startup should not be blocked by JWKS fetch failuresSet asyncFetch.fastListener: true to allow traffic to flow while the JWKS fetch happens in the background.
Authentication must fail closed until JWKS is availableKeep fastListener: false (default) to block traffic until the JWKS is successfully fetched.
Seeing frequent network failuresIncrease retries, but keep the max backoff bounded so failures surface quickly. Avoid setting very high retry counts or very long backoff intervals because that can make real JWKS endpoint outages harder to detect.

Warning

Important: Setting fastListener: true means that requests might be rejected if the JWKS fetch fails before it completes, because the gateway cannot validate tokens without the JWKS. Consider your application’s availability requirements when choosing this setting.

JWKS fetch timeout

The timeout field sets how long the gateway waits for the remote JWKS server to respond to a single fetch. It bounds one attempt, so it works alongside retryPolicy, which decides how many attempts are made and how long to wait between them.

jwks:
  remote:
    url: $KEYCLOAK_URL/realms/master/protocol/openid-connect/certs
    backendRef:
      name: keycloak
      kind: Backend
      group: gateway.kgateway.dev
    timeout: 10s
FieldDescription
jwks.remote.timeoutHow long the gateway waits for the remote JWKS server to respond when it fetches signing keys. Accepts Go duration strings of up to 32 characters, and must be at least 1ms. If unset, the gateway waits 5s.

JWT caching

You can enable Envoy JWT caching for verified tokens in a JWT provider configuration. The cache stores tokens that already passed signature verification, so repeated requests with the same token do not repeat the parse, JWKS lookup, and signature verification work.

Set the spec.jwt.providers[].cache block on the GatewayExtension resource to turn on caching for a provider. Caching supports three configurations:

  • Omit the cache block to keep caching disabled.
  • Set cache: {} (an empty object) to enable caching with Envoy’s default settings, which include 100 tokens per worker thread and a 4096-byte max token size.
  • Set cache.size and/or cache.maxTokenSize to override the Envoy default value. If you omit a field, the Envoy default setting is applied.

The following example sets the custom cache size and max token size values:

apiVersion: gateway.kgateway.dev/v1alpha1
kind: GatewayExtension
metadata:
  name: selfminted-jwt
spec:
  jwt:
    providers:
      - name: selfminted
        issuer: kgateway.dev
        cache:
          size: 1024
          maxTokenSize: 8192
        jwks:
          local:
            inline: '{"keys":[{"kty":"RSA","kid":"kgateway-public-key-001","use":"sig","alg":"RS256","n":"...","e":"AQAB"}]}'

To enable caching with Envoy’s defaults instead of overriding them, set cache to an empty object:

apiVersion: gateway.kgateway.dev/v1alpha1
kind: GatewayExtension
metadata:
  name: selfminted-jwt
spec:
  jwt:
    providers:
      - name: selfminted
        issuer: kgateway.dev
        cache: {}
        jwks:
          local:
            inline: '{"keys":[{"kty":"RSA","kid":"kgateway-public-key-001","use":"sig","alg":"RS256","n":"...","e":"AQAB"}]}'
FieldDescription
cacheEnables Envoy JWT caching for this provider. An empty object turns on caching with Envoy defaults.
cache.sizeThe number of verified tokens to cache per Envoy worker thread. Must be at least 1 if set. If unset, Envoy uses 100. The effective number of cached tokens for the whole proxy is cache.size multiplied by the number of Envoy worker threads.
cache.maxTokenSizeThe maximum size of one cached token in bytes. Must be at least 1 if set. If unset, Envoy uses 4096.

Caching does not extend a token’s validity. Envoy caches only verified tokens, checks token time constraints on each cache hit, and removes expired tokens from the cache.

By default, the proxy does not set Envoy’s --concurrency or --cpuset-threads flags, so it uses one worker thread per CPU that Envoy detects on the node, not the CPU request or limit set on the proxy pod. To make the worker thread count follow the pod’s CPU limit instead, add --cpuset-threads (or a fixed --concurrency <N>) to envoyContainer.extraArgs on the GatewayParameters resource. For more information, see Change proxy config.

Clock skew tolerance

Use clockSkew to set how much drift to tolerate between the proxy’s clock and the identity provider’s clock when the exp and nbf claims are verified. Use when a token that is still valid at the issuer arrives at the proxy as expired or not-yet-valid, such as when the identity provider runs outside the cluster or on a host with an unsynchronized clock.

apiVersion: gateway.kgateway.dev/v1alpha1
kind: GatewayExtension
metadata:
  name: selfminted-jwt
spec:
  jwt:
    providers:
      - name: selfminted
        issuer: kgateway.dev
        clockSkew: 90s
        jwks:
          local:
            inline: '{"keys":[{"kty":"RSA","kid":"kgateway-public-key-001","use":"sig","alg":"RS256","n":"...","e":"AQAB"}]}'
FieldDescription
clockSkewHow much clock drift the gateway tolerates when it verifies the exp and nbf claims. Accepts a duration with no sub-second component, from 1s up to 87600h (10 years), such as 30s, 90s, or 1h30m. Sub-second values such as 500ms, a value of 0s, and anything above 87600h are rejected. If unset, the gateway tolerates 60s, meaning it still accepts a token up to 60 seconds after its exp or up to 60 seconds before its nbf.

Set clockSkew only as wide as the clock drift you actually observe, because a wider tolerance also accepts tokens for longer after they expire. Where you control both the proxy and the identity provider, synchronize their clocks with NTP instead of widening the tolerance.

JWT validation modes

Use the validationMode field to control whether requests without a JWT are allowed.

Requests with valid tokens populate JWT dynamic metadata and any headers that you configure in the claimsToHeaders field.

In all validation modes, JWT verification failures are written to Envoy dynamic metadata at envoy.filters.http.jwt_authn:failed_status. Use the %DYNAMIC_METADATA(envoy.filters.http.jwt_authn:failed_status)% expression in access logs to view the requests that failed the JWT validation. Note that failures are still captured, even if you choose a validation mode that allows through invalid-token requests, such as AllowMissingOrFailed.

Strict (default)

Requests without a valid JWT are rejected with a 401 Unauthorized response.

kubectl apply -f- <<EOF
apiVersion: gateway.kgateway.dev/v1alpha1
kind: GatewayExtension
metadata:
  name: selfminted-jwt
  namespace: kgateway-system
spec:
  jwt:
    validationMode: Strict
    providers:
      - name: selfminted
        issuer: kgateway.dev
        jwks:
          local:
            inline: '{"keys":[{"kty":"RSA","kid":"kgateway-public-key-001","use":"sig","alg":"RS256","n":"tNxnW0ZghyIUdfRc97EuZ6Hii0z4AucJrbOCT8MxKznlnV9Z-OrOYMf_hyjiD2Q_qyGrv-sRhinKOjokr-cbLKhHlAlEkEW1ah4wQ-zzO3DT0SdAKX_7RkMkl5Sba443vfDlDmuVSBeyHQr6cKZZGBIe8TlzcKR0xYlop13p1DYAHsIiX8A_q2CmsRlnV4CbneNMGZOmHuBiFG3DJ2lc1ZgvKc8SN1gt3oEujRqxy4yPLHVJ3wQ58ezYtgV2gzbyllzJdi1DSoPtnCFFGvfDqmAcDdmfVtHUHqagCF0ivEQsrxt7PYKqxuCbkaSY1_ef7ub01_5KF1GhlA9y5XSqJQ","e":"AQAB"}]}'
EOF

AllowMissing

Requests without a token are allowed through. Requests that present an invalid token are still rejected. When you use the AllowMissing validation mode, pair it with an RBAC policy to enforce authorization based on JWT claims as unauthenticated requests are allowed through. For an example, see Restrict access based on claims.

kubectl apply -f- <<EOF
apiVersion: gateway.kgateway.dev/v1alpha1
kind: GatewayExtension
metadata:
  name: selfminted-jwt
  namespace: kgateway-system
spec:
  jwt:
    validationMode: AllowMissing
    providers:
      - name: selfminted
        issuer: kgateway.dev
        jwks:
          local:
            inline: '{"keys":[{"kty":"RSA","kid":"kgateway-public-key-001","use":"sig","alg":"RS256","n":"tNxnW0ZghyIUdfRc97EuZ6Hii0z4AucJrbOCT8MxKznlnV9Z-OrOYMf_hyjiD2Q_qyGrv-sRhinKOjokr-cbLKhHlAlEkEW1ah4wQ-zzO3DT0SdAKX_7RkMkl5Sba443vfDlDmuVSBeyHQr6cKZZGBIe8TlzcKR0xYlop13p1DYAHsIiX8A_q2CmsRlnV4CbneNMGZOmHuBiFG3DJ2lc1ZgvKc8SN1gt3oEujRqxy4yPLHVJ3wQ58ezYtgV2gzbyllzJdi1DSoPtnCFFGvfDqmAcDdmfVtHUHqagCF0ivEQsrxt7PYKqxuCbkaSY1_ef7ub01_5KF1GhlA9y5XSqJQ","e":"AQAB"}]}'
EOF

AllowMissingOrFailed

Use the AllowMissingOrFailed validation mode to evaluate a JWT policy against live traffic before you enforce it. The JWT filter verifies each token that a request sends, but the filter allows the request through when the token is missing, expired, malformed, or otherwise invalid.

Warning

AllowMissingOrFailed provides no authentication. A downstream RBAC policy that matches JWT claims receives empty JWT metadata for invalid-token requests, the same as requests that send no token. If you set claimsToHeaders, invalid-token requests also reach the upstream without the configured claim headers.

kubectl apply -f- <<EOF
apiVersion: gateway.kgateway.dev/v1alpha1
kind: GatewayExtension
metadata:
  name: selfminted-jwt
  namespace: kgateway-system
spec:
  jwt:
    validationMode: AllowMissingOrFailed
    providers:
      - name: selfminted
        issuer: kgateway.dev
        jwks:
          local:
            inline: '{"keys":[{"kty":"RSA","kid":"kgateway-public-key-001","use":"sig","alg":"RS256","n":"tNxnW0ZghyIUdfRc97EuZ6Hii0z4AucJrbOCT8MxKznlnV9Z-OrOYMf_hyjiD2Q_qyGrv-sRhinKOjokr-cbLKhHlAlEkEW1ah4wQ-zzO3DT0SdAKX_7RkMkl5Sba443vfDlDmuVSBeyHQr6cKZZGBIe8TlzcKR0xYlop13p1DYAHsIiX8A_q2CmsRlnV4CbneNMGZOmHuBiFG3DJ2lc1ZgvKc8SN1gt3oEujRqxy4yPLHVJ3wQ58ezYtgV2gzbyllzJdi1DSoPtnCFFGvfDqmAcDdmfVtHUHqagCF0ivEQsrxt7PYKqxuCbkaSY1_ef7ub01_5KF1GhlA9y5XSqJQ","e":"AQAB"}]}'
EOF

If a request would have failed the JWT verification, the failure is written to Envoy dynamic metadata at envoy.filters.http.jwt_authn:failed_status. Use the %DYNAMIC_METADATA(envoy.filters.http.jwt_authn:failed_status)% expression in access logs to view these requests.

The following table compares how each validation mode responds to the same requests.

RequestStrictAllowMissingAllowMissingOrFailed
Valid token200200200
No token401200200
Invalid token401401200

Configure audiences

Restrict access to tokens that include a specific audience claim. An incoming token must include an aud claim that matches at least one of the listed values. The following example restricts access to tokens that include a my-api audience.

Warning

The sample token in this guide has no aud claim, so using this configuration requires a different token that carries a matching aud claim.

kubectl apply -f- <<EOF
apiVersion: gateway.kgateway.dev/v1alpha1
kind: GatewayExtension
metadata:
  name: selfminted-jwt
  namespace: kgateway-system
spec:
  jwt:
    providers:
      - name: selfminted
        issuer: kgateway.dev
        audiences:
          - my-api
        jwks:
          local:
            inline: '{"keys":[{"kty":"RSA","kid":"kgateway-public-key-001","use":"sig","alg":"RS256","n":"tNxnW0ZghyIUdfRc97EuZ6Hii0z4AucJrbOCT8MxKznlnV9Z-OrOYMf_hyjiD2Q_qyGrv-sRhinKOjokr-cbLKhHlAlEkEW1ah4wQ-zzO3DT0SdAKX_7RkMkl5Sba443vfDlDmuVSBeyHQr6cKZZGBIe8TlzcKR0xYlop13p1DYAHsIiX8A_q2CmsRlnV4CbneNMGZOmHuBiFG3DJ2lc1ZgvKc8SN1gt3oEujRqxy4yPLHVJ3wQ58ezYtgV2gzbyllzJdi1DSoPtnCFFGvfDqmAcDdmfVtHUHqagCF0ivEQsrxt7PYKqxuCbkaSY1_ef7ub01_5KF1GhlA9y5XSqJQ","e":"AQAB"}]}'
EOF

Send a request with the sample token to verify audience enforcement:

curl -vik http://$INGRESS_GW_ADDRESS:8080/headers \
  -H "host: www.example.com:8080" \
  --header "Authorization: Bearer $TOKEN"

Example output:

< HTTP/1.1 401 Unauthorized
Jwt validation failed: audience mismatch

Customize token source

By default, the gateway reads the JWT from the Authorization header as a bearer token. Use tokenSource to read the token from a different header or from a URL query parameter. The following example reads the token from a custom x-jwt header with a Bearer prefix.

Warning

Changing the tokenSource affects how clients must send requests. If you apply this example, send the token in the x-jwt header instead of the Authorization header.

kubectl apply -f- <<EOF
apiVersion: gateway.kgateway.dev/v1alpha1
kind: GatewayExtension
metadata:
  name: selfminted-jwt
  namespace: kgateway-system
spec:
  jwt:
    providers:
      - name: selfminted
        issuer: kgateway.dev
        tokenSource:
          header:
            header: x-jwt
            prefix: "Bearer "
        jwks:
          local:
            inline: '{"keys":[{"kty":"RSA","kid":"kgateway-public-key-001","use":"sig","alg":"RS256","n":"tNxnW0ZghyIUdfRc97EuZ6Hii0z4AucJrbOCT8MxKznlnV9Z-OrOYMf_hyjiD2Q_qyGrv-sRhinKOjokr-cbLKhHlAlEkEW1ah4wQ-zzO3DT0SdAKX_7RkMkl5Sba443vfDlDmuVSBeyHQr6cKZZGBIe8TlzcKR0xYlop13p1DYAHsIiX8A_q2CmsRlnV4CbneNMGZOmHuBiFG3DJ2lc1ZgvKc8SN1gt3oEujRqxy4yPLHVJ3wQ58ezYtgV2gzbyllzJdi1DSoPtnCFFGvfDqmAcDdmfVtHUHqagCF0ivEQsrxt7PYKqxuCbkaSY1_ef7ub01_5KF1GhlA9y5XSqJQ","e":"AQAB"}]}'
EOF

Send a request with the token in the x-jwt header:

curl -vik http://$INGRESS_GW_ADDRESS:8080/headers \
  -H "host: www.example.com:8080" \
  -H "x-jwt: Bearer $TOKEN"

Example output:

< HTTP/1.1 200 OK

Forward tokens upstream

By default, the gateway strips the JWT from the request before forwarding it to the upstream service. Set forwardToken: true to keep the token so the upstream service can read it.

kubectl apply -f- <<EOF
apiVersion: gateway.kgateway.dev/v1alpha1
kind: GatewayExtension
metadata:
  name: selfminted-jwt
  namespace: kgateway-system
spec:
  jwt:
    providers:
      - name: selfminted
        issuer: kgateway.dev
        forwardToken: true
        jwks:
          local:
            inline: '{"keys":[{"kty":"RSA","kid":"kgateway-public-key-001","use":"sig","alg":"RS256","n":"tNxnW0ZghyIUdfRc97EuZ6Hii0z4AucJrbOCT8MxKznlnV9Z-OrOYMf_hyjiD2Q_qyGrv-sRhinKOjokr-cbLKhHlAlEkEW1ah4wQ-zzO3DT0SdAKX_7RkMkl5Sba443vfDlDmuVSBeyHQr6cKZZGBIe8TlzcKR0xYlop13p1DYAHsIiX8A_q2CmsRlnV4CbneNMGZOmHuBiFG3DJ2lc1ZgvKc8SN1gt3oEujRqxy4yPLHVJ3wQ58ezYtgV2gzbyllzJdi1DSoPtnCFFGvfDqmAcDdmfVtHUHqagCF0ivEQsrxt7PYKqxuCbkaSY1_ef7ub01_5KF1GhlA9y5XSqJQ","e":"AQAB"}]}'
EOF

Send a request with the token to verify it is forwarded to the upstream:

curl -vik http://$INGRESS_GW_ADDRESS:8080/headers \
  -H "host: www.example.com:8080" \
  --header "Authorization: Bearer $TOKEN"

Example output:

{
  "headers": {
    ...
    "Authorization": [
      "Bearer eyJhbGciOiJSUzI1NiIs..."
    ]
  }
}

For claim-based access control with a CEL rbac policy, see Restrict access with claim-based rules.

Disable JWT filter

The disable field lets you turn off JWT authentication at a higher policy level. This is useful when you want to override a JWT policy applied at the Gateway level for a specific HTTPRoute.

1. Send a request without a JWT to verify it’s blocked:

curl -vik http://$INGRESS_GW_ADDRESS:8080/headers -H "host: www.example.com:8080"

Expected output:

< HTTP/1.1 401 Unauthorized
Jwt is missing

2. Disable JWT with the TrafficPolicy:

kubectl apply -f- <<EOF
apiVersion: gateway.kgateway.dev/v1alpha1
kind: TrafficPolicy
metadata:
  name: jwt-disable
  namespace: kgateway-system
spec:
  targetRefs:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      name: httpbin
  jwtAuth:
    disable: {}
EOF

In this example, any JWT policy applied at the Gateway level is disabled for the httpbin HTTPRoute. Requests to this route can be made without a JWT.

3. Repeat the request, now verifying it succeeds because JWT authentication is disabled:

curl -vik http://$INGRESS_GW_ADDRESS:8080/headers -H "host: www.example.com:8080"

Expected output:

< HTTP/1.1 200 OK

Cleanup

kubectl delete TrafficPolicy jwt-policy -n kgateway-system
kubectl delete gatewayextension selfminted-jwt -n kgateway-system

If you completed the Remote JWKS section, also delete the Backend that you created for the Keycloak server.

kubectl delete backend keycloak -n kgateway-system
Was this page helpful?