For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.
Keycloak
Use this guide to protect an HTTPRoute with Keycloak as an OIDC provider. kgateway handles the OAuth2 authorization code flow: unauthenticated browser requests get redirected to Keycloak, the code is exchanged for tokens, and those tokens are stored in session cookies. Your upstream service doesn’t need to know any of this happened.
You need three resources: a Backend pointing at your Keycloak host, a GatewayExtension to configure the OAuth2 provider, and a TrafficPolicy to attach it to a route.
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
-
A running Keycloak instance with a configured realm and OIDC client. At minimum, set the following on the Keycloak client:
Setting Value Client ID Any name, you will reference it in the GatewayExtensionClient authentication On (this makes it a confidential client with a secret) Valid redirect URIs https://GATEWAY_HOST/oauth2/redirectWeb origins https://GATEWAY_HOSTThe redirect URI path is
/oauth2/redirect, which is the default callback path that kgateway registers. You can override it withredirectURIin theGatewayExtensionif needed.
Store the client secret
Create a Kubernetes Secret with the Keycloak client secret. kgateway reads the value from the client-secret key specifically, so the key name matters.
kubectl create secret generic keycloak-client-secret \
--from-literal=client-secret=YOUR_CLIENT_SECRET \
-n kgateway-systemGrab YOUR_CLIENT_SECRET from the Credentials tab of your Keycloak client.
Create a Backend for Keycloak
kgateway needs a Backend resource to reach the Keycloak host. This is what backendRef in the GatewayExtension points to.
kubectl apply -f- <<EOF
apiVersion: gateway.kgateway.dev/v1alpha1
kind: Backend
metadata:
name: keycloak
namespace: kgateway-system
spec:
type: Static
static:
hosts:
- host: keycloak.example.com
port: 443
EOFReplace keycloak.example.com with your Keycloak hostname. The port must be 443 because kgateway communicates with Keycloak over HTTPS.
Create the GatewayExtension
The GatewayExtension holds everything the gateway needs to talk to Keycloak. The GatewayExtension is independent of routing, so you can reuse the same extension across multiple TrafficPolicy resources.
kubectl apply -f- <<EOF
apiVersion: gateway.kgateway.dev/v1alpha1
kind: GatewayExtension
metadata:
name: keycloak-oauth2
namespace: kgateway-system
spec:
oauth2:
backendRef:
name: keycloak
namespace: kgateway-system
issuerURI: https://keycloak.example.com/realms/myrealm
authorizationEndpoint: https://keycloak.example.com/realms/myrealm/protocol/openid-connect/auth
tokenEndpoint: https://keycloak.example.com/realms/myrealm/protocol/openid-connect/token
endSessionEndpoint: https://keycloak.example.com/realms/myrealm/protocol/openid-connect/logout
scopes:
- openid
- email
- profile
credentials:
clientID: kgateway-client
clientSecretRef:
name: keycloak-client-secret
EOFReplace keycloak.example.com and myrealm with your Keycloak host and realm.
Field notes:
backendRefpoints to theBackendcreated in the previous step. kgateway uses it to reach Keycloak for token exchange and OIDC discovery.issuerURItriggers OIDC discovery. kgateway fetches/.well-known/openid-configurationfrom this URL and fills in the authorization, token, and end-session endpoints. If you also set those explicitly (as in the example), the explicit values win. Setting both is fine if you want the config to be readable without relying on discovery.scopesdefaults touserif not set. For OIDC you needopenidin the list. Addemailandprofileif your app needs those claims.endSessionEndpointhandles single logout. When a user hits/logout, kgateway clears their session cookies and sends their browser to this URL so Keycloak ends the session too. This is RP-initiated logout in the OIDC spec. Only set it if your realm has that feature enabled andopenidis in your scopes.clientSecretRef.namemust match the Secret name from the previous step. kgateway reads theclient-secretkey inside that Secret.
Attach the policy
Create a TrafficPolicy that references the extension by name. This policy tells the gateway to enforce the login flow on a specific route.
Note: The OAuth2 filter does not protect against CSRF attacks on routes with cached authentication cookies. Pair the OAuth2 filter with a
CSRFPolicyon the same route, especially for browser-facing apps.
kubectl apply -f- <<EOF
apiVersion: gateway.kgateway.dev/v1alpha1
kind: TrafficPolicy
metadata:
name: keycloak-oauth2
namespace: kgateway-system
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: httpbin
oauth2:
extensionRef:
name: keycloak-oauth2
namespace: kgateway-system
EOFtargetRefs can point to a specific HTTPRoute or all routes in a Gateway. This example locks down a single route named httpbin.
Configure cookie settings
kgateway stores the access and ID tokens in session cookies. The default SameSite policy is Lax. If you need custom cookie names (for example, to read them in downstream services or share across subdomains), set them explicitly under cookies on the GatewayExtension.
spec:
oauth2:
# ... rest of the provider config ...
cookies:
sameSite: Strict
names:
accessToken: kgw-access
idToken: kgw-idStrict means the browser won’t send cookies on any cross-site request, including top-level navigations. Use Lax, which is the default, if users arrive at your app through links from other origins, like an email link. None requires HTTPS and should only be used when you explicitly need cross-site cookie sharing.
Add this block to the GatewayExtension manifest from the previous step and re-apply.
Stop redirecting API clients
By default, any unauthenticated request gets a 302 redirect to the Keycloak login page. That response is what you want for a browser, but not for API clients. curl, mobile apps, and AJAX calls that hit an unauthenticated route will silently follow the redirect, land on the Keycloak login HTML, and fail.
The denyRedirect field on OAuth2Provider lets you match specific requests and return 401 instead of redirecting them. It takes a list of HTTPHeaderMatch entries, and a request matches if it satisfies all of them.
Pattern for matching JSON API clients:
spec:
oauth2:
# ... rest of the provider config ...
denyRedirect:
headers:
- name: Accept
type: Exact
value: application/jsonFor requests that might send Accept: application/json; charset=utf-8 or similar variations, you can use RegularExpression:
denyRedirect:
headers:
- name: Accept
type: RegularExpression
value: "application/json.*"For AJAX requests from browser JavaScript:
denyRedirect:
headers:
- name: X-Requested-With
type: Exact
value: XMLHttpRequestThe full GatewayExtension with denyRedirect included:
kubectl apply -f- <<EOF
apiVersion: gateway.kgateway.dev/v1alpha1
kind: GatewayExtension
metadata:
name: keycloak-oauth2
namespace: kgateway-system
spec:
oauth2:
backendRef:
name: keycloak
namespace: kgateway-system
issuerURI: https://keycloak.example.com/realms/myrealm
authorizationEndpoint: https://keycloak.example.com/realms/myrealm/protocol/openid-connect/auth
tokenEndpoint: https://keycloak.example.com/realms/myrealm/protocol/openid-connect/token
endSessionEndpoint: https://keycloak.example.com/realms/myrealm/protocol/openid-connect/logout
scopes:
- openid
- email
- profile
credentials:
clientID: kgateway-client
clientSecretRef:
name: keycloak-client-secret
denyRedirect:
headers:
- name: Accept
type: Exact
value: application/json
EOFVerify
-
Send a request without a session cookie. The gateway should redirect to Keycloak.
curl -vi "http://${INGRESS_GW_ADDRESS}:8080/headers" -H "host: www.example.com"Example output:
< HTTP/1.1 302 Found < location: https://keycloak.example.com/realms/myrealm/protocol/openid-connect/auth?client_id=kgateway-client&... -
Send the same request with
Accept: application/json. BecausedenyRedirectmatches on this header, the gateway returns401directly instead of redirecting.curl -vi "http://${INGRESS_GW_ADDRESS}:8080/headers" \ -H "host: www.example.com" \ -H "Accept: application/json"Example output:
< HTTP/1.1 401 Unauthorized
Cleanup
You can remove the resources that you created in this guide.kubectl delete TrafficPolicy keycloak-oauth2 -n kgateway-system
kubectl delete GatewayExtension keycloak-oauth2 -n kgateway-system
kubectl delete Backend keycloak -n kgateway-system
kubectl delete secret keycloak-client-secret -n kgateway-system