-
Notifications
You must be signed in to change notification settings - Fork 85
docs(security): add OAuth2/OIDC integration guide for Keycloak #733
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mihir-dixit2k27
wants to merge
1
commit into
kgateway-dev:main
Choose a base branch
from
mihir-dixit2k27:docs/add-oauth2-oidc-keycloak-guide
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,241 @@ | ||
|
|
||
| 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 two resources: a `GatewayExtension` to configure the provider (endpoints, client credentials, cookie behavior), and a `TrafficPolicy` to wire that extension to a route. | ||
|
|
||
| ## Before you begin | ||
|
|
||
| {{< reuse "docs/snippets/prereq.md" >}} | ||
|
|
||
| 4. 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'll reference it in the `GatewayExtension` | | ||
| | **Client authentication** | On (this makes it a confidential client with a secret) | | ||
| | **Valid redirect URIs** | `https://<your-gateway-host>/oauth2/redirect` | | ||
| | **Web origins** | `https://<your-gateway-host>` | | ||
|
|
||
| The redirect URI path is `/oauth2/redirect`, which is the default callback path that kgateway registers. You can override it with `redirectURI` in the `GatewayExtension` if needed. | ||
|
|
||
| ## Store the client secret {#store-credentials} | ||
|
|
||
| Create a Kubernetes Secret with the Keycloak client secret. kgateway reads the value from the `client-secret` key specifically, so the key name matters. | ||
|
|
||
| ```shell | ||
| kubectl create secret generic keycloak-client-secret \ | ||
| --from-literal=client-secret=YOUR_CLIENT_SECRET \ | ||
| -n {{< reuse "docs/snippets/namespace.md" >}} | ||
| ``` | ||
|
|
||
| Grab `YOUR_CLIENT_SECRET` from the **Credentials** tab of your Keycloak client. | ||
|
|
||
| ## Create the GatewayExtension {#create-extension} | ||
|
|
||
| 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. | ||
|
|
||
| ```yaml | ||
| kubectl apply -f- <<EOF | ||
| apiVersion: {{< reuse "docs/snippets/trafficpolicy-apiversion.md" >}} | ||
| kind: GatewayExtension | ||
| metadata: | ||
| name: keycloak-oauth2 | ||
| namespace: {{< reuse "docs/snippets/namespace.md" >}} | ||
| spec: | ||
| oauth2: | ||
| 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 | ||
| - profile | ||
| credentials: | ||
| clientID: kgateway-client | ||
| clientSecretRef: | ||
| name: keycloak-client-secret | ||
| EOF | ||
| ``` | ||
|
|
||
| Replace `keycloak.example.com` and `myrealm` with your Keycloak host and realm. | ||
|
|
||
| A few things worth knowing about these fields: | ||
|
|
||
| - `issuerURI` triggers OIDC discovery. kgateway fetches `/.well-known/openid-configuration` from 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. | ||
| - `scopes` defaults to `user` if not set. For OIDC you need `openid` in the list. Add `email` and `profile` if your app needs those claims. | ||
| - `endSessionEndpoint` enables single logout across both kgateway and Keycloak. When a user hits `/logout`, kgateway clears their session cookies and redirects their browser to this URL, which tells Keycloak to end the session on its side too (this is called RP-initiated logout in the OIDC spec). Only set this if your Keycloak realm has that feature enabled and `openid` is in your scopes. | ||
| - `clientSecretRef.name` must match the Secret name from the previous step. kgateway reads the `client-secret` key inside that Secret. | ||
|
|
||
| ## Attach the policy {#attach-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 `CSRFPolicy` on the same route, especially for browser-facing apps. | ||
|
|
||
| ```yaml | ||
| kubectl apply -f- <<EOF | ||
| apiVersion: {{< reuse "docs/snippets/trafficpolicy-apiversion.md" >}} | ||
| kind: {{< reuse "docs/snippets/trafficpolicy.md" >}} | ||
| metadata: | ||
| name: keycloak-oauth2 | ||
| namespace: {{< reuse "docs/snippets/namespace.md" >}} | ||
| spec: | ||
| targetRefs: | ||
| - group: gateway.networking.k8s.io | ||
| kind: HTTPRoute | ||
| name: httpbin | ||
| oauth2: | ||
| extensionRef: | ||
| name: keycloak-oauth2 | ||
| namespace: {{< reuse "docs/snippets/namespace.md" >}} | ||
| EOF | ||
| ``` | ||
|
|
||
| `targetRefs` can point to a specific `HTTPRoute` or all routes in a `Gateway`. This example locks down a single route named `httpbin`. | ||
|
|
||
| ## Configure cookie settings {#cookie-config} | ||
|
|
||
| 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`. | ||
|
|
||
| ```yaml | ||
| spec: | ||
| oauth2: | ||
| # ... rest of the provider config ... | ||
| cookies: | ||
| sameSite: Strict | ||
| names: | ||
| accessToken: kgw-access | ||
| idToken: kgw-id | ||
| ``` | ||
|
|
||
| `Strict` 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 {#deny-redirect} | ||
|
|
||
| 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: | ||
|
|
||
| ```yaml | ||
| spec: | ||
| oauth2: | ||
| # ... rest of the provider config ... | ||
| denyRedirect: | ||
| headers: | ||
| - name: Accept | ||
| type: Exact | ||
| value: application/json | ||
| ``` | ||
|
|
||
| For requests that might send `Accept: application/json; charset=utf-8` or similar variations, you can use `RegularExpression`: | ||
|
|
||
| ```yaml | ||
| denyRedirect: | ||
| headers: | ||
| - name: Accept | ||
| type: RegularExpression | ||
| value: "application/json.*" | ||
| ``` | ||
|
|
||
| For AJAX requests from browser JavaScript: | ||
|
|
||
| ```yaml | ||
| denyRedirect: | ||
| headers: | ||
| - name: X-Requested-With | ||
| type: Exact | ||
| value: XMLHttpRequest | ||
| ``` | ||
|
|
||
| The full `GatewayExtension` with `denyRedirect` included: | ||
|
|
||
| ```yaml | ||
| kubectl apply -f- <<EOF | ||
| apiVersion: {{< reuse "docs/snippets/trafficpolicy-apiversion.md" >}} | ||
| kind: GatewayExtension | ||
| metadata: | ||
| name: keycloak-oauth2 | ||
| namespace: {{< reuse "docs/snippets/namespace.md" >}} | ||
| spec: | ||
| oauth2: | ||
| 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 | ||
| - profile | ||
| credentials: | ||
| clientID: kgateway-client | ||
| clientSecretRef: | ||
| name: keycloak-client-secret | ||
| denyRedirect: | ||
| headers: | ||
| - name: Accept | ||
| type: Exact | ||
| value: application/json | ||
| EOF | ||
| ``` | ||
|
|
||
| ## Verify {#verify} | ||
|
|
||
| 1. Send a request without a session cookie. The gateway should redirect to Keycloak. | ||
|
|
||
| {{< tabs tabTotal="2" items="Cloud Provider LoadBalancer,Port-forward for local testing" >}} | ||
| {{% tab tabName="Cloud Provider LoadBalancer" %}} | ||
| ```sh | ||
| curl -vi "http://${INGRESS_GW_ADDRESS}:8080/headers" -H "host: www.example.com" | ||
| ``` | ||
| {{% /tab %}} | ||
| {{% tab tabName="Port-forward for local testing" %}} | ||
| ```sh | ||
| curl -vi "http://localhost:8080/headers" -H "host: www.example.com" | ||
| ``` | ||
| {{% /tab %}} | ||
| {{< /tabs >}} | ||
|
|
||
| Example output: | ||
| ``` | ||
| < HTTP/1.1 302 Found | ||
| < location: https://keycloak.example.com/realms/myrealm/protocol/openid-connect/auth?client_id=kgateway-client&... | ||
| ``` | ||
|
|
||
| 2. Send the same request with `Accept: application/json`. Because `denyRedirect` matches on this header, the gateway returns `401` directly instead of redirecting. | ||
|
|
||
| {{< tabs tabTotal="2" items="Cloud Provider LoadBalancer,Port-forward for local testing" >}} | ||
| {{% tab tabName="Cloud Provider LoadBalancer" %}} | ||
| ```sh | ||
| curl -vi "http://${INGRESS_GW_ADDRESS}:8080/headers" \ | ||
| -H "host: www.example.com" \ | ||
| -H "Accept: application/json" | ||
| ``` | ||
| {{% /tab %}} | ||
| {{% tab tabName="Port-forward for local testing" %}} | ||
| ```sh | ||
| curl -vi "http://localhost:8080/headers" \ | ||
| -H "host: www.example.com" \ | ||
| -H "Accept: application/json" | ||
| ``` | ||
| {{% /tab %}} | ||
| {{< /tabs >}} | ||
|
|
||
| Example output: | ||
| ``` | ||
| < HTTP/1.1 401 Unauthorized | ||
| ``` | ||
|
|
||
| ## Cleanup {#cleanup} | ||
|
|
||
| {{< reuse "docs/snippets/cleanup.md" >}} | ||
|
|
||
| ```sh | ||
| kubectl delete {{< reuse "docs/snippets/trafficpolicy.md" >}} keycloak-oauth2 -n {{< reuse "docs/snippets/namespace.md" >}} | ||
| kubectl delete GatewayExtension keycloak-oauth2 -n {{< reuse "docs/snippets/namespace.md" >}} | ||
| kubectl delete secret keycloak-client-secret -n {{< reuse "docs/snippets/namespace.md" >}} | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| --- | ||
| title: OAuth2/OIDC | ||
| weight: 15 | ||
| description: Protect routes with browser-based OAuth2/OIDC login flows backed by any standards-compliant identity provider. | ||
| --- | ||
|
|
||
| kgateway has built-in support for the OAuth2 authorization code flow. Point a `GatewayExtension` at your provider, attach it to a route via `TrafficPolicy`, and the gateway handles the rest - login redirects, token exchange, and session cookies - without touching your upstream service. | ||
|
|
||
| {{< cards >}} | ||
| {{< card link="keycloak" title="Keycloak" >}} | ||
| {{< /cards >}} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| title: Keycloak | ||
| weight: 10 | ||
| description: Set up OAuth2/OIDC authentication with Keycloak to protect routes through kgateway's built-in authorization code flow. | ||
| --- | ||
|
|
||
| {{< reuse "docs/pages/security/oauth2-keycloak.md" >}} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| --- | ||
| title: OAuth2/OIDC | ||
| weight: 15 | ||
| description: Protect routes with browser-based OAuth2/OIDC login flows backed by any standards-compliant identity provider. | ||
| --- | ||
|
|
||
| kgateway has built-in support for the OAuth2 authorization code flow. Point a `GatewayExtension` at your provider, attach it to a route via `TrafficPolicy`, and the gateway handles the rest - login redirects, token exchange, and session cookies - without touching your upstream service. | ||
|
|
||
| {{< cards >}} | ||
| {{< card link="keycloak" title="Keycloak" >}} | ||
| {{< /cards >}} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| title: Keycloak | ||
| weight: 10 | ||
| description: Set up OAuth2/OIDC authentication with Keycloak to protect routes through kgateway's built-in authorization code flow. | ||
| --- | ||
|
|
||
| {{< reuse "docs/pages/security/oauth2-keycloak.md" >}} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| --- | ||
| title: OAuth2/OIDC | ||
|
mihir-dixit2k27 marked this conversation as resolved.
|
||
| weight: 15 | ||
| description: Protect routes with browser-based OAuth2/OIDC login flows backed by any standards-compliant identity provider. | ||
| --- | ||
|
|
||
| kgateway has built-in support for the OAuth2 authorization code flow. Point a `GatewayExtension` at your provider, attach it to a route via `TrafficPolicy`, and the gateway handles the rest — login redirects, token exchange, and session cookies — without touching your upstream service. | ||
|
|
||
| {{< cards >}} | ||
| {{< card link="keycloak" title="Keycloak" >}} | ||
| {{< /cards >}} | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| title: Keycloak | ||
| weight: 10 | ||
| description: Set up OAuth2/OIDC authentication with Keycloak to protect routes through kgateway's built-in authorization code flow. | ||
| --- | ||
|
|
||
| {{< reuse "docs/pages/security/oauth2-keycloak.md" >}} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.