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.

Page as Markdown

    

Basic JWT policy

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: solo.io
            jwks:
              local:
                inline: '{"keys":[{"kty":"RSA","kid":"solo-public-key-001","use":"sig","alg":"RS256","n":"AOfIaJMUm7564sWWNHaXt_hS8H0O1Ew59-nRqruMQosfQqa7tWne5lL3m9sMAkfa3Twx0LMN_7QqRDoztvV3Wa_JwbMzb9afWE-IfKIuDqkvog6s-xGIFNhtDGBTuL8YAQYtwCF7l49SMv-GqyLe-nO9yJW-6wIGoOqImZrCxjxXFzF6mTMOBpIODFj0LUZ54QQuDcD1Nue2LMLsUvGa7V1ZHsYuGvUqzvXFBXMmMS2OzGir9ckpUhrUeHDCGFpEM4IQnu-9U8TbAJxKE5Zp8Nikefr2ISIG2Hk1K2rBAc_HwoPeWAcAWUAR5tWHAxx-UXClSZQ9TMFK850gQGenUp8","e":"AQAB"}]}'
    EOF
    Field Description
    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
    Field Description
    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

    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=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNvbG8tcHVibGljLWtleS0wMDEifQ.eyJpc3MiOiJzb2xvLmlvIiwib3JnIjoic29sby5pbyIsInN1YiI6ImFsaWNlIiwidGVhbSI6ImRldiIsImV4cCI6MjA3NDI3NDg4NCwibGxtcyI6eyJvcGVuYWkiOlsiZ3B0LTMuNS10dXJibyJdfX0.il5Rjsad65jpQR_pyRzBdEKFSj-ERmBf4K2VksvGvswWVv4n79lYERslr4KCECuiz9y_T-xUiQ9IkhW3YHzl5zo1kajhhIg7Nhnl1AvAqODbnF6wYpLRk0Npna_2T6lK3Yj54qQGi6vXG3IMRpo1_o2DrbdlKx2k_WFegCoQyyYazb4z3ZXfWvTiWqQDJA5wWcM3-jKzAWfNM8zgZWa-1BeAHDvpLcfWtuXEGSjkdCW0FQJOTjgIEqACnnXb2Jio0tWgelh9hDPILI-tvanj3iKCjpf3uF6g8QWSBNoVFfu7F1jJgj5Aj1sX8AV-CQVu2aQx3EHRZ1mL_3w3qSRWPw
    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: solo.io
            claimsToHeaders:
              - name: team
                header: x-team
              - name: org
                header: x-org
            jwks:
              local:
                inline: '{"keys":[{"kty":"RSA","kid":"solo-public-key-001","use":"sig","alg":"RS256","n":"AOfIaJMUm7564sWWNHaXt_hS8H0O1Ew59-nRqruMQosfQqa7tWne5lL3m9sMAkfa3Twx0LMN_7QqRDoztvV3Wa_JwbMzb9afWE-IfKIuDqkvog6s-xGIFNhtDGBTuL8YAQYtwCF7l49SMv-GqyLe-nO9yJW-6wIGoOqImZrCxjxXFzF6mTMOBpIODFj0LUZ54QQuDcD1Nue2LMLsUvGa7V1ZHsYuGvUqzvXFBXMmMS2OzGir9ckpUhrUeHDCGFpEM4IQnu-9U8TbAJxKE5Zp8Nikefr2ISIG2Hk1K2rBAc_HwoPeWAcAWUAR5tWHAxx-UXClSZQ9TMFK850gQGenUp8","e":"AQAB"}]}'
    EOF
    Field Description
    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": [
          "solo.io"
        ],
        "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
    Field Description
    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.

JWT validation modes

The validationMode field in spec.jwt controls whether requests without a JWT are allowed. To change the mode, reapply the GatewayExtension that you created earlier with the updated validationMode value.

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: solo.io
        jwks:
          local:
            inline: '{"keys":[{"kty":"RSA","kid":"solo-public-key-001","use":"sig","alg":"RS256","n":"AOfIaJMUm7564sWWNHaXt_hS8H0O1Ew59-nRqruMQosfQqa7tWne5lL3m9sMAkfa3Twx0LMN_7QqRDoztvV3Wa_JwbMzb9afWE-IfKIuDqkvog6s-xGIFNhtDGBTuL8YAQYtwCF7l49SMv-GqyLe-nO9yJW-6wIGoOqImZrCxjxXFzF6mTMOBpIODFj0LUZ54QQuDcD1Nue2LMLsUvGa7V1ZHsYuGvUqzvXFBXMmMS2OzGir9ckpUhrUeHDCGFpEM4IQnu-9U8TbAJxKE5Zp8Nikefr2ISIG2Hk1K2rBAc_HwoPeWAcAWUAR5tWHAxx-UXClSZQ9TMFK850gQGenUp8","e":"AQAB"}]}'
EOF

AllowMissing: Requests without a token are allowed through. Requests that present an invalid token are still rejected. When you use AllowMissing, pair it with an RBAC policy to enforce authorization, because 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: solo.io
        jwks:
          local:
            inline: '{"keys":[{"kty":"RSA","kid":"solo-public-key-001","use":"sig","alg":"RS256","n":"AOfIaJMUm7564sWWNHaXt_hS8H0O1Ew59-nRqruMQosfQqa7tWne5lL3m9sMAkfa3Twx0LMN_7QqRDoztvV3Wa_JwbMzb9afWE-IfKIuDqkvog6s-xGIFNhtDGBTuL8YAQYtwCF7l49SMv-GqyLe-nO9yJW-6wIGoOqImZrCxjxXFzF6mTMOBpIODFj0LUZ54QQuDcD1Nue2LMLsUvGa7V1ZHsYuGvUqzvXFBXMmMS2OzGir9ckpUhrUeHDCGFpEM4IQnu-9U8TbAJxKE5Zp8Nikefr2ISIG2Hk1K2rBAc_HwoPeWAcAWUAR5tWHAxx-UXClSZQ9TMFK850gQGenUp8","e":"AQAB"}]}'
EOF

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.

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: solo.io
        audiences:
          - my-api
        jwks:
          local:
            inline: '{"keys":[{"kty":"RSA","kid":"solo-public-key-001","use":"sig","alg":"RS256","n":"AOfIaJMUm7564sWWNHaXt_hS8H0O1Ew59-nRqruMQosfQqa7tWne5lL3m9sMAkfa3Twx0LMN_7QqRDoztvV3Wa_JwbMzb9afWE-IfKIuDqkvog6s-xGIFNhtDGBTuL8YAQYtwCF7l49SMv-GqyLe-nO9yJW-6wIGoOqImZrCxjxXFzF6mTMOBpIODFj0LUZ54QQuDcD1Nue2LMLsUvGa7V1ZHsYuGvUqzvXFBXMmMS2OzGir9ckpUhrUeHDCGFpEM4IQnu-9U8TbAJxKE5Zp8Nikefr2ISIG2Hk1K2rBAc_HwoPeWAcAWUAR5tWHAxx-UXClSZQ9TMFK850gQGenUp8","e":"AQAB"}]}'
EOF

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.

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: solo.io
        tokenSource:
          header:
            header: x-jwt
            prefix: "Bearer "
        jwks:
          local:
            inline: '{"keys":[{"kty":"RSA","kid":"solo-public-key-001","use":"sig","alg":"RS256","n":"AOfIaJMUm7564sWWNHaXt_hS8H0O1Ew59-nRqruMQosfQqa7tWne5lL3m9sMAkfa3Twx0LMN_7QqRDoztvV3Wa_JwbMzb9afWE-IfKIuDqkvog6s-xGIFNhtDGBTuL8YAQYtwCF7l49SMv-GqyLe-nO9yJW-6wIGoOqImZrCxjxXFzF6mTMOBpIODFj0LUZ54QQuDcD1Nue2LMLsUvGa7V1ZHsYuGvUqzvXFBXMmMS2OzGir9ckpUhrUeHDCGFpEM4IQnu-9U8TbAJxKE5Zp8Nikefr2ISIG2Hk1K2rBAc_HwoPeWAcAWUAR5tWHAxx-UXClSZQ9TMFK850gQGenUp8","e":"AQAB"}]}'
EOF

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: solo.io
        forwardToken: true
        jwks:
          local:
            inline: '{"keys":[{"kty":"RSA","kid":"solo-public-key-001","use":"sig","alg":"RS256","n":"AOfIaJMUm7564sWWNHaXt_hS8H0O1Ew59-nRqruMQosfQqa7tWne5lL3m9sMAkfa3Twx0LMN_7QqRDoztvV3Wa_JwbMzb9afWE-IfKIuDqkvog6s-xGIFNhtDGBTuL8YAQYtwCF7l49SMv-GqyLe-nO9yJW-6wIGoOqImZrCxjxXFzF6mTMOBpIODFj0LUZ54QQuDcD1Nue2LMLsUvGa7V1ZHsYuGvUqzvXFBXMmMS2OzGir9ckpUhrUeHDCGFpEM4IQnu-9U8TbAJxKE5Zp8Nikefr2ISIG2Hk1K2rBAc_HwoPeWAcAWUAR5tWHAxx-UXClSZQ9TMFK850gQGenUp8","e":"AQAB"}]}'
EOF

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

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?