This document gives a step-by-step tutorial of getting started with the AuthProxy in a local Kind cluster.
The final architecture deployed is as follows:
Caller ──► AuthProxy Pod ──► Demo App
(inbound: JWT processing)
(outbound HTTP: token exchange via ext_proc)
(outbound HTTPS: TLS passthrough via tcp_proxy)
The AuthProxy pod intercepts traffic in both directions:
- Inbound: Processes JWT tokens on incoming requests (validates if present, returns 401 if invalid)
- Outbound HTTP: Exchanges tokens for the correct audience before forwarding to the Demo App
- Outbound HTTPS: Envoy detects TLS and passes traffic through as-is (no ext_proc, no token exchange)
The demo goes as follows:
- Install Kagenti
- Build and deploy the Demo App and AuthProxy
- Configure Keycloak
- Create the auth-proxy-config secret
- Test the flow
First, we recommend to deploy Kagenti to a local Kind cluster with the Ansible installer as service urls used below are derived from that installation. Instructions are available here.
This should start a local Kind cluster named kagenti.
The key component is Keycloak which has been deployed to the keycloak namespace and exposed as keycloak-service.
Let's clone the assets locally:
git clone git@github.com:kagenti/kagenti-extensions.git
cd kagenti-extensions/authbridge/authproxyWe can use the following make commands to build and load the images to the Kind cluster:
make build-images
make load-imagesIf the above gives error ERROR: no nodes found... set the KIND_CLUSTER_NAME environment variable to the name of the kind cluster you are using.
Then we can create two deployments in Kubernetes:
make deployPort-forward Keycloak to access it locally (in a separate terminal):
kubectl port-forward service/keycloak-service -n keycloak 8080:8080Now set up a Python environment and run the setup script:
cd quickstart
python -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
python setup_keycloak.pyThe script creates:
application-callerclient - for obtaining initial tokens (password grant)authproxyclient - used by the AuthProxy sidecar for token exchangedemoappclient - target audience for token exchangeauthproxy-audscope - addsauthproxyto token audiencedemoapp-audscope - addsdemoappto exchanged token audiencetest-user/password- demo user for testing
The AuthProxy sidecar needs credentials for token exchange. Get the authproxy client secret and create the Kubernetes secret:
# Get admin token
ADMIN_TOKEN=$(curl -s -X POST "http://keycloak.localtest.me:8080/realms/master/protocol/openid-connect/token" \
-d "client_id=admin-cli" -d "grant_type=password" -d "username=admin" -d "password=admin" | jq -r '.access_token')
# Get authproxy client secret
AUTHPROXY_SECRET=$(curl -s -H "Authorization: Bearer $ADMIN_TOKEN" \
"http://keycloak.localtest.me:8080/admin/realms/kagenti/clients?clientId=authproxy" | jq -r '.[0].secret')
# Create the secret
kubectl create secret generic auth-proxy-config \
--from-literal=TOKEN_URL="http://keycloak-service.keycloak.svc.cluster.local:8080/realms/kagenti/protocol/openid-connect/token" \
--from-literal=ISSUER="http://keycloak.localtest.me:8080/realms/kagenti" \
--from-literal=CLIENT_ID="authproxy" \
--from-literal=CLIENT_SECRET="$AUTHPROXY_SECRET"Then restart the auth-proxy deployment to pick up the secret:
kubectl rollout restart deployment auth-proxy
kubectl rollout status deployment auth-proxy --timeout=120sPort-forward the AuthProxy service. Use port 9080 since Keycloak is already using 8080:
kubectl port-forward svc/auth-proxy-service 9080:8080Wait for the ext proc to initialize (it takes up to 60 seconds to load credentials on first startup), then get a token and test:
# Get application-caller client secret
ADMIN_TOKEN=$(curl -s -X POST "http://keycloak.localtest.me:8080/realms/master/protocol/openid-connect/token" \
-d "client_id=admin-cli" -d "grant_type=password" -d "username=admin" -d "password=admin" | jq -r '.access_token')
APP_SECRET=$(curl -s -H "Authorization: Bearer $ADMIN_TOKEN" \
"http://keycloak.localtest.me:8080/admin/realms/kagenti/clients?clientId=application-caller" | jq -r '.[0].secret')
# Get an access token (password grant with test-user)
export ACCESS_TOKEN=$(curl -s -X POST \
"http://keycloak.localtest.me:8080/realms/kagenti/protocol/openid-connect/token" \
-d "grant_type=password" \
-d "client_id=application-caller" \
-d "client_secret=$APP_SECRET" \
-d "username=test-user" \
-d "password=password" \
-d "scope=openid authproxy-aud" | jq -r '.access_token')Valid request (inbound validation passes, token exchange, forwarded to demo-app):
curl -H "Authorization: Bearer $ACCESS_TOKEN" http://localhost:9080/test
# Expected response: "authorized"This exercises the full outbound HTTP path: Envoy intercepts the outbound request via http_connection_manager, the ext_proc exchanges the token for the demoapp audience, and demo-app validates the JWT.
Invalid token (rejected by demo-app JWT validation):
curl -H "Authorization: Bearer invalid-token" http://localhost:9080/test
# Expected response: "unauthorized"No authorization header (rejected by demo-app JWT validation):
curl http://localhost:9080/test
# Expected response: "unauthorized: missing Authorization header"Public endpoint — no authentication required:
curl http://localhost:9080/.well-known/agent.json
# Expected: JSON AgentCard with agent name, skills, protocol versionThe /.well-known/agent.json endpoint is a public A2A discovery endpoint that does not require JWT authentication. This is the use case motivating route-based auth bypass (see issue #124) — public discovery endpoints like AgentCard must be accessible without a token, even when the rest of the application is protected by AuthBridge.
HTTPS connectivity through Envoy TLS passthrough:
curl -H "Authorization: Bearer $ACCESS_TOKEN" http://localhost:9080/tls-test
# Expected response: "tls-ok"This exercises the outbound HTTPS path: the auth-proxy makes an HTTPS request to demo-app-service:8443, Envoy detects TLS via tls_inspector and forwards it as-is through tcp_proxy (no ext_proc, no token exchange). The demo-app HTTPS port serves a simple echo response without JWT validation — it only proves HTTPS connectivity through TLS passthrough works.
No authorization header (passes through — no JWT validation on this path):
curl http://localhost:9080/tls-test
# Expected response: "tls-ok"Note: The HTTPS path has no JWT validation at either end. The inbound ext_proc processes tokens when present but does not enforce that a token must exist. The outbound HTTPS path uses TLS passthrough (no ext_proc), and the demo-app HTTPS port has no JWT validation. Authentication on the HTTP path (/test) is enforced by the demo-app itself, which validates the exchanged token.
When deployed to Kubernetes, you can test the services internally:
Test demo app directly:
kubectl run test-pod --image=curlimages/curl --rm -it --restart=Never -- curl -H "Authorization: Bearer $ACCESS_TOKEN" http://demo-app-service:8081/testView logs:
# Auth proxy logs (pass-through proxy)
kubectl logs deployment/auth-proxy -c auth-proxy
# Envoy proxy + ext proc logs (inbound validation and outbound token exchange)
kubectl logs deployment/auth-proxy -c envoy-proxy
# Demo app logs
kubectl logs deployment/demo-app
# Follow logs in real-time
kubectl logs -f deployment/auth-proxy -c envoy-proxyCheck service status:
# List pods
kubectl get pods
# List services
kubectl get svc
# Describe deployments
kubectl describe deployment auth-proxy
kubectl describe deployment demo-appRemove Kubernetes deployment:
make undeploy
kubectl delete secret auth-proxy-config --ignore-not-found=trueDelete kind cluster:
make kind-delete