Keycloak and Istio can work together to enforce OIDC authentication for traffic within your service mesh, meaning your services only receive requests that have been pre-vetted by Keycloak.

Let’s see this in action. Imagine you have a simple frontend service that needs to call a backend service. We’ll configure Istio to intercept traffic to backend and require a valid OIDC token issued by Keycloak.

Here’s a simplified frontend service making a call to backend:

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
)

func main() {
	backendURL := "http://backend.default.svc.cluster.local/data"
	resp, err := http.Get(backendURL)
	if err != nil {
		fmt.Printf("Error calling backend: %v\n", err)
		os.Exit(1)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("Error reading backend response: %v\n", err)
		os.Exit(1)
	}
	fmt.Printf("Backend response: %s\n", body)
}

And a simple backend service:

package main

import (
	"fmt"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Data from backend!")
}

func main() {
	http.HandleFunc("/data", handler)
	fmt.Println("Backend listening on :8080")
	http.ListenAndServe(":8080", nil)
}

Now, the magic happens with Istio’s configuration. We’ll use an AuthorizationPolicy to enforce the OIDC requirement.

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: backend-authz
  namespace: default
spec:
  selector:
    matchLabels:
      app: backend
  action: ALLOW
  rules:
  - to:
    - operation:
        methods: ["GET"]
    when:
    - key: "request.auth.claims[iss]"
      values: ["http://keycloak.keycloak.svc.cluster.local/realms/myrealm"] # The issuer URL from Keycloak
    - key: "request.auth.claims[aud]"
      values: ["my-backend-client"] # The audience configured for your Keycloak client
    - key: "request.auth.claims[email]" # Example of checking a specific claim
      values: ["user@example.com"]

This AuthorizationPolicy tells Istio’s sidecar proxies (Envoy) that for any GET request to a service with the label app: backend, it must have a valid JWT. This JWT needs to be issued by our Keycloak instance (checked by iss), intended for our backend service (checked by aud), and optionally, contain specific claims like email.

For this to work, you also need to have Keycloak deployed and configured within your cluster, and Istio’s OIDC discovery enabled. This typically involves configuring the meshConfig.defaultConfig.gatewayMesh or meshConfig.defaultConfig.proxy.oidcDiscovery settings in your Istio operator or IstioOperator CRD.

apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  meshConfig:
    defaultConfig:
      proxy:
        oidcDiscovery:
          enabled: true
          oidcDiscoveryConfig:
            issuer: http://keycloak.keycloak.svc.cluster.local/realms/myrealm
            tokenEndpoint: http://keycloak.keycloak.svc.cluster.local/realms/myrealm/protocol/openid-connect/token
            userInfoEndpoint: http://keycloak.keycloak.svc.cluster.local/realms/myrealm/protocol/openid-connect/userinfo

When the frontend service makes a request to backend, Istio’s sidecar on the frontend (or an Istio Gateway if you’re using one) will intercept it. If no JWT is present, or if the JWT is invalid, the request will be rejected with a 401 Unauthorized. If a valid JWT is present, the request will be forwarded to the backend service. The backend service itself doesn’t need any specific authentication logic; the security is enforced upstream by Istio.

The critical piece here is that Istio’s Envoy proxies are configured to validate JWTs against Keycloak’s JWKS (JSON Web Key Set) endpoint. This means they can cryptographically verify the signature of the incoming JWT using Keycloak’s public keys. The AuthorizationPolicy then acts as the policy enforcement point, inspecting the claims within the validated JWT to make access control decisions.

A common point of confusion is how the JWT actually gets into the request. In a typical setup, you’d have an authentication service or an Ingress Gateway that handles the initial user login with Keycloak. Upon successful authentication, Keycloak issues a JWT, which is then returned to the client. This client then includes this JWT in the Authorization: Bearer <token> header for subsequent requests to services within the mesh. If you’re using Istio’s RequestAuthentication resource, it can also be configured to automatically fetch tokens from an upstream identity provider like Keycloak and inject them into downstream requests, abstracting this client-side responsibility.

The next hurdle you’ll likely encounter is managing token refresh and handling expired tokens gracefully across your services.

Want structured learning?

Take the full Keycloak course →