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
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
-
Follow the Get started guide to install kgateway.
-
Follow the Sample app guide to create a gateway proxy with an HTTP listener and deploy the httpbin sample app.
-
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
-
Create a GatewayExtension resource with a
jwtconfiguration. 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"}]}' EOFField 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 issclaim. Tokens with a different issuer are rejected. If omitted, theissfield 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. -
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 EOFField 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 kindfield 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. -
Send a request without a JWT and verify that you get a
401 Unauthorizedresponse.curl -vik http://$INGRESS_GW_ADDRESS:8080/headers -H "host: www.example.com:8080"Example output:
< HTTP/1.1 401 Unauthorized Jwt is missingGot a
200 OKinstead? 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 statusBoth resources must show an
Acceptedcondition. If either has no status at all, the resource could be in the wrong namespace. -
Save a sample JWT token and send it in the
Authorizationheader. 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_3w3qSRWPwcurl -vik http://$INGRESS_GW_ADDRESS:8080/headers \ -H "host: www.example.com:8080" \ --header "Authorization: Bearer $TOKEN"Verify that you get a
200 OKresponse.
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.
-
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
teamandorgclaims from the verified JWT and forwards them to the upstream service as thex-teamandx-orgheaders.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"}]}' EOFField 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. -
Send the request again with the JWT. Verify that the response includes the
X-TeamandX-Orgheaders, which the gateway extracted from the token’steamandorgclaims 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.
-
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 -
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 -
Update the GatewayExtension to use the Keycloak JWKS endpoint. Set the
issuerto the Keycloak realm URL and theurlto the realm’s JWKS endpoint. ThebackendRefpoints 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 EOFField 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 kindtoBackendandgrouptogateway.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"}]}'
EOFAllowMissing: 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"}]}'
EOFConfigure 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.
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"}]}'
EOFCustomize 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.
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"}]}'
EOFForward 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"}]}'
EOFFor 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-systemIf you completed the Remote JWKS section, also delete the Backend that you created for the Keycloak server.
kubectl delete backend keycloak -n kgateway-system