This guide walks through deploying the Weather Service Agent with AuthBridge using the Kagenti UI for agent and tool deployment. Infrastructure setup (webhook, Keycloak, ConfigMaps) is done via CLI, while the agent and tool are imported and deployed through the Kagenti dashboard.
This is the recommended getting-started demo for AuthBridge. It demonstrates inbound JWT validation and automatic identity registration with a simple agent that doesn't require token exchange. For a more advanced demo showing outbound token exchange and scope-based access control, see the GitHub Issue Agent demo. For the same weather images with token exchange and AuthBridge on the tool (plus a CI-style verify script), see Weather Agent — Advanced.
- Agent identity — The agent automatically registers with Keycloak using its SPIFFE ID, with no hardcoded secrets
- Inbound validation — Requests to the agent are validated (JWT signature, issuer, and audience) before reaching the agent code
- Transparent outbound passthrough — When the agent calls the weather tool, AuthBridge passes the request through without modification (default outbound policy), so agents work out-of-the-box with any tool or LLM provider
- Zero code changes — The agent and tool source code require no modifications; all security is handled by AuthBridge sidecars
┌──────────────────────────────────────────────────────────────────────────────────┐
│ KUBERNETES CLUSTER │
│ │
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
│ │ WEATHER-SERVICE POD (namespace: team1) │ │
│ │ │ │
│ │ ┌──────────────────┐ ┌─────────────┐ ┌──────────────────────────────┐ │ │
│ │ │ weather-service │ │ spiffe- │ │ client-registration │ │ │
│ │ │ (A2A agent, │ │ helper │ │ (registers with Keycloak │ │ │
│ │ │ port 8000) │ │ │ │ using SPIFFE ID) │ │ │
│ │ └──────────────────┘ └─────────────┘ └──────────────────────────────┘ │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────────────────────┐ │ │
│ │ │ AuthProxy Sidecar (envoy-proxy container) │ │ │
│ │ │ Envoy + ext_proc (authbridge) │ │ │
│ │ │ Inbound (port 15124): │ │ │
│ │ │ - Validates JWT (signature + issuer + audience via JWKS) │ │ │
│ │ │ - Returns 401 Unauthorized for invalid/missing tokens │ │ │
│ │ │ Outbound (port 15123): │ │ │
│ │ │ - HTTP: Passthrough (default policy, no token exchange) │ │ │
│ │ │ - HTTPS: TLS passthrough (no interception) │ │ │
│ │ └───────────────────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ Plain HTTP call │(no token exchange) │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
│ │ WEATHER-TOOL POD (namespace: team1) │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────────────────┐ │ │
│ │ │ weather-tool (port 8000) │ │ │
│ │ │ - MCP server: provides get_weather tool │ │ │
│ │ │ - Calls public weather API (Open-Meteo) │ │ │
│ │ └──────────────────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────────────┘ │
│ │
├──────────────────────────────────────────────────────────────────────────────────┤
│ EXTERNAL SERVICES │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ SPIRE (namespace: │ │ KEYCLOAK (namespace: │ │
│ │ spire) │ │ keycloak) │ │
│ │ │ │ │ │
│ │ Provides SPIFFE │ │ - kagenti realm │ │
│ │ identities (SVIDs) │ │ - JWKS for inbound │ │
│ │ │ │ JWT validation │ │
│ └──────────────────────┘ └──────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────────┘
Ensure you have completed the Kagenti platform setup as described in the Installation Guide, including the Kagenti UI.
You should also have:
- The Kagenti UI running at
http://kagenti-ui.localtest.me:8080 - An LLM provider — either:
- Ollama running locally with a model (e.g.
llama3.2:3b-instruct-fp16), or - OpenAI API key (recommended for most reliable results; see agent-examples#173 for known Ollama + crewai compatibility issues)
- Ollama running locally with a model (e.g.
The Kagenti installer creates everything this demo needs in the target namespace:
kagentirealm in Keycloakkeycloak-admin-secretSecret (Keycloak admin credentials)authbridge-config,authbridge-runtime-config,spiffe-helper-config,envoy-configConfigMaps
No additional Keycloak configuration, Secrets, or ConfigMaps are required for this demo. The weather agent uses outbound passthrough (no token exchange), and inbound JWT validation works with signature and issuer checks alone.
If your Keycloak admin credentials differ from the default (
admin/admin), update the secret:kubectl create secret generic keycloak-admin-secret -n team1 \ --from-literal=KEYCLOAK_ADMIN_USERNAME=<your-admin-user> \ --from-literal=KEYCLOAK_ADMIN_PASSWORD=<your-admin-password> \ --dry-run=client -o yaml | kubectl apply -f -
-
Navigate to Import Tool in the Kagenti UI.
-
In the Namespace drop-down, choose
team1, fill Tool Name withweather-tool(do not use uppercase) -
Select Deploy From Image as the deployment method.
-
For Container Image, use
ghcr.io/kagenti/agent-examples/weather_tool. -
Pick a corresponding Image Tag, replace the default
v0.0.1withlatest. -
Set MCP Transport Protocol to
streamable HTTP. -
Enable AuthBridge sidecar injection is unchecked by default for tools. Leave it unchecked.
-
Enable SPIRE identity (spiffe-helper sidecar) should be unchecked.
The weather tool is a simple MCP server calling a public weather API. It does not need AuthBridge sidecars or token validation.
-
Click Deploy Tool and the button should change to "Deploying". If it is not changing review input for errors.
kubectl get pods -n team1 | grep weather-tool
# Expected: weather-tool-xxxx 1/1 Running 0 ...-
Navigate to Import Agent in the Kagenti UI.
-
In the Namespace drop-down, choose
team1. -
Select Build from Source as the deployment method.
-
Under Source Repository select:
- Git Repository URL:
https://github.com/kagenti/agent-examples - Git Branch or Tag:
main - Select Agent:
Weather Service Agent - Source Subfolder:
a2a/weather_service
- Git Repository URL:
-
Protocol:
A2A -
Framework:
LangGraph -
Workload Type select
Deployment. -
Enable AuthBridge sidecar injection is checked by default for agents. Leave it checked.
-
Enable SPIRE identity (spiffe-helper sidecar) is checked by default. Leave it checked.
-
Under Port Configuration, set Service Port to
8080and Target Port to8000 -
Under Environment Variables, click Import from File/URL, Select From URL and provide the URL from this repo:
- For Ollama:
https://raw.githubusercontent.com/kagenti/agent-examples/refs/heads/main/a2a/weather_service/.env.ollama - For OpenAI:
https://raw.githubusercontent.com/kagenti/agent-examples/refs/heads/main/a2a/weather_service/.env.openai - Click Fetch & Parse — this populates all environment variables including
LLM settings and
MCP_URL. No manual editing is needed. - Click Import to set all the env. variables.
The Ollama variant sets all direct values. The OpenAI variant includes Secret type entries referencing
openai-secretforLLM_API_KEYandOPENAI_API_KEY.Tip: You can also upload the file directly from your local system. OpenAI prerequisite: If using OpenAI, create the secret first:
kubectl create secret generic openai-secret -n team1 \ --from-literal=apikey="<YOUR_OPENAI_API_KEY>"If you had empty string for "openaiApiKey:" in .secret_values.yaml the secret with empty string is already created so delete it if you get "error: failed to create secret secrets "openai-secret" already exists"
kubectl delete secret openai-secret -n team1
- For Ollama:
-
(Ollama only) If using Ollama as your LLM provider, expand AuthBridge Advanced Configuration and enter
11434in the Outbound Ports to Exclude field. This prevents AuthBridge from intercepting traffic to Ollama on the host machine. OpenAI users can skip this — HTTPS traffic passes through via TLS passthrough. -
Click Build & Deploy Agent.
Wait for the Shipwright build to complete and the deployment to become ready.
kubectl get pods -n team1Expected output:
NAME READY STATUS RESTARTS AGE
weather-service-58768bdb67-xxxxx 4/4 Running 0 2m
weather-tool-7f8c9d6b44-yyyyy 1/1 Running 0 5m
Note: The agent pod should show 4/4 containers — the agent itself plus three AuthBridge sidecars (spiffe-helper, kagenti-client-registration, envoy-proxy) injected by the webhook.
kubectl get pod -n team1 -l app.kubernetes.io/name=weather-service -o jsonpath='{.items[0].spec.containers[*].name}'Expected:
agent kagenti-client-registration envoy-proxy spiffe-helper
kubectl logs deployment/weather-service -n team1 -c kagenti-client-registrationExpected:
SPIFFE credentials ready!
Client ID (SPIFFE ID): spiffe://localtest.me/ns/team1/sa/weather-service
Created Keycloak client "spiffe://localtest.me/ns/team1/sa/weather-service"
Client registration complete!
kubectl logs deployment/weather-service -n team1 -c agentExpected:
INFO: Started server process [17]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
kubectl get svc -n team1 | grep weather-serviceExpected:
weather-service ClusterIP 10.96.x.x <none> 8080/TCP 5m
The service maps port 8080 to the agent's internal port 8000.
The agent uses an LLM for inference. Follow the section that matches your chosen provider.
Verify Ollama is running:
ollama listYou should see llama3.2:3b-instruct-fp16 (or whichever model you configured) on
the list. If Ollama is not running, start it in a separate terminal (ollama serve)
and ensure the model is pulled (ollama pull llama3.2:3b-instruct-fp16).
Note: The
.env.ollamafile defaults toLLM_API_BASE=http://host.docker.internal:11434/v1, which reaches Ollama running on your host machine via the Kind/Docker Desktop gateway. If you deploy Ollama inside the cluster instead, patch the agent:kubectl set env deployment/weather-service -n team1 -c agent \ LLM_API_BASE="http://ollama.ollama.svc:11434/v1"
AuthBridge's proxy-init init container redirects traffic through Envoy. By
default, only port 8080 (Keycloak) is excluded. Ollama traffic on port 11434
gets intercepted, which corrupts LLM streaming responses.
If you set the Outbound Ports to Exclude field to 11434 during import
(Step 2, item 12), this is already handled and no patch is needed.
Otherwise, add the annotation after deployment:
kubectl patch deployment weather-service -n team1 --type=merge -p='
{"spec":{"template":{"metadata":{"annotations":{"kagenti.io/outbound-ports-exclude":"11434"}}}}}'
kubectl rollout status deployment/weather-service -n team1 --timeout=120sVerify the OpenAI secret exists (see the prerequisite note in Step 2):
kubectl get secret openai-secret -n team1Verify the agent has the correct environment variables:
kubectl exec deployment/weather-service -n team1 -c agent -- env | grep -E "LLM_|OPENAI"Expected:
LLM_API_BASE=https://api.openai.com/v1
LLM_MODEL=gpt-4o-mini-2024-07-18
LLM_API_KEY=sk-...
OPENAI_API_KEY=sk-...
Note: OpenAI uses HTTPS, which AuthBridge passes through via TLS passthrough. No Ollama port exclusion workaround is needed.
- Navigate to the Agent Catalog in the Kagenti UI.
- Select the
team1namespace. - Under Available Agents, select
weather-serviceand click View Details. - Verify the Agent Card is visible (this confirms the agent is running and
the
/.well-known/*bypass is working). - Use the Chat panel to send a message, e.g. "What is the weather in New York?".
- The agent should respond with current weather information.
Troubleshooting: If UI chat returns a
401, verify that both the UI and AuthBridge are configured against the samekagentirealm. You can also use Step 6: Test via CLI to test the AuthBridge flow independently.
Test the AuthBridge flow from the command line to verify inbound validation.
# Start a test client pod
kubectl run test-client --image=nicolaka/netshoot -n team1 --restart=Never -- sleep 3600
kubectl wait --for=condition=ready pod/test-client -n team1 --timeout=30sThe /.well-known/agent.json endpoint is publicly accessible — authbridge
bypasses JWT validation for /.well-known/*, /healthz, /readyz,
and /livez by default:
kubectl exec test-client -n team1 -- curl -s \
http://weather-service:8080/.well-known/agent.json | jq .name
# Expected: "weather_service"Non-public endpoints require a valid JWT:
kubectl exec test-client -n team1 -- curl -s \
http://weather-service:8080/
# Expected: {"error":"unauthorized","message":"missing Authorization header"}A malformed or tampered token fails the JWKS signature check:
kubectl exec test-client -n team1 -- curl -s \
-H "Authorization: Bearer invalid-token" \
http://weather-service:8080/
# Expected: {"error":"unauthorized","message":"token validation failed: failed to parse/validate token: ..."}Open a shell inside the test-client pod to avoid JWT shell expansion issues:
kubectl exec -it test-client -n team1 -- shInside the pod, get credentials and send a request:
# Get a Keycloak admin token from the kagenti realm
ADMIN_TOKEN=$(curl -s http://keycloak-service.keycloak.svc:8080/realms/kagenti/protocol/openid-connect/token \
-d "grant_type=password" \
-d "client_id=admin-cli" \
-d "username=admin" \
-d "password=admin" | jq -r ".access_token")
echo "Admin token length: ${#ADMIN_TOKEN}"
# Look up the agent's client in the kagenti realm
SPIFFE_ID="spiffe://localtest.me/ns/team1/sa/weather-service"
CLIENTS=$(curl -s -H "Authorization: Bearer $ADMIN_TOKEN" \
"http://keycloak-service.keycloak.svc:8080/admin/realms/kagenti/clients" \
--data-urlencode "clientId=$SPIFFE_ID" --get)
CLIENT_ID=$(echo "$CLIENTS" | jq -r ".[0].clientId")
CLIENT_SECRET=$(echo "$CLIENTS" | jq -r ".[0].secret")
echo "Client ID: $CLIENT_ID"
echo "Secret length: ${#CLIENT_SECRET}"
# Get an OAuth token for the agent
TOKEN=$(curl -s -X POST \
"http://keycloak-service.keycloak.svc:8080/realms/kagenti/protocol/openid-connect/token" \
-d "grant_type=client_credentials" \
--data-urlencode "client_id=$CLIENT_ID" \
--data-urlencode "client_secret=$CLIENT_SECRET" | jq -r ".access_token")
echo "Token length: ${#TOKEN}"
# Send a prompt to the agent (A2A v0.3.0)
curl -s --max-time 300 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-X POST http://weather-service:8080/ \
-d '{
"jsonrpc": "2.0",
"id": "test-1",
"method": "message/send",
"params": {
"message": {
"role": "user",
"messageId": "msg-001",
"parts": [{"type": "text", "text": "What is the weather in New York?"}]
}
}
}' | jqExit the pod when done:
exitCheck the authbridge logs to confirm inbound validation is working:
# For envoy-sidecar mode:
kubectl logs deployment/weather-service -n team1 -c envoy-proxy 2>&1 | grep "inbound authorized"
# For proxy-sidecar mode:
kubectl logs deployment/weather-service -n team1 -c authbridge-proxy 2>&1 | grep "inbound authorized"Expected:
level=INFO msg="inbound authorized" subject=... clientID=kagenti
Tip: For detailed debug logs (audience, scopes, request path), enable debug logging — see Debug Logging below.
kubectl delete pod test-client -n team1 --ignore-not-foundSymptom: {"error":"invalid_client","error_description":"Invalid client or Invalid client credentials"}
Cause: The keycloak-admin-secret Secret or authbridge-config ConfigMap was missing
or incorrect at startup, so the client-registration sidecar couldn't register the client.
Fix:
# 1. Verify the keycloak-admin-secret exists
kubectl get secret keycloak-admin-secret -n team1
# 2. Verify the authbridge-config ConfigMap has the correct realm
kubectl get configmap authbridge-config -n team1 -o jsonpath='{.data.KEYCLOAK_REALM}'
# Should show: kagenti
# 3. Restart the agent to retry registration
kubectl rollout restart deployment/weather-service -n team1Symptom: Agent fails to start or can't reach the weather tool
Cause: The UI deployment didn't include all required environment variables.
Fix: Patch the deployment directly:
kubectl set env deployment/weather-service -n team1 -c agent \
MCP_URL="http://mcp-weather-tool-headless:8000/mcp"
kubectl rollout status deployment/weather-service -n team1 --timeout=180sSymptom: upstream request timeout from Envoy
Cause: The LLM inference takes longer than the Envoy route timeout.
Fix: The installer's envoy-config ConfigMap sets route and ext_proc
timeouts to 300 seconds (5 min). If you still hit timeouts, verify the
ConfigMap has the correct values:
kubectl get configmap envoy-config -n team1 -o jsonpath='{.data.envoy\.yaml}' | grep "timeout:"If you see 30s values instead of 300s, reinstall Kagenti (the installer
creates the correct defaults) and restart the agent:
kubectl rollout restart deployment/weather-service -n team1This demo normally does not create authproxy-routes. If the UI still cannot load the
agent card while the agent container responds on port 8000, Envoy’s ext_proc path is
likely broken—often due to invalid authproxy-routes YAML left over from another
workflow or namespace reuse. Follow Agent card not available in the
GitHub Issue Agent UI demo
(check envoy-proxy logs and fix or remove authproxy-routes as described there).
Symptom: Pod shows 3/4 or less containers ready
Fix: Check each container's logs:
kubectl logs deployment/weather-service -n team1 -c kagenti-client-registration
kubectl logs deployment/weather-service -n team1 -c spiffe-helper
kubectl logs deployment/weather-service -n team1 -c envoy-proxy
kubectl logs deployment/weather-service -n team1 -c agentBy default, the weather agent deploys in envoy-sidecar mode (Envoy + iptables). You can switch to proxy-sidecar mode which uses a lightweight reverse/forward proxy (29 MB image, no Envoy, no iptables).
Add the mode annotation to the deployment:
kubectl patch deployment weather-service -n team1 --type=merge \
-p '{"spec":{"template":{"metadata":{"annotations":{"kagenti.io/authbridge-mode":"proxy-sidecar"}}}}}'
kubectl rollout status deployment weather-service -n team1 --timeout=120s# Containers: agent, authbridge-proxy, spiffe-helper, kagenti-client-registration
# No envoy-proxy or proxy-init
kubectl get pod -n team1 -l app.kubernetes.io/name=weather-service \
-o jsonpath='{range .spec.containers[*]}{.name}{"\n"}{end}'
# AuthBridge should show mode=proxy-sidecar
kubectl logs deployment/weather-service -n team1 -c authbridge-proxy --tail=5Remove the annotation (envoy-sidecar is the default):
kubectl patch deployment weather-service -n team1 --type=json \
-p '[{"op":"remove","path":"/spec/template/metadata/annotations/kagenti.io~1authbridge-mode"}]'
kubectl rollout status deployment weather-service -n team1 --timeout=120s| envoy-sidecar (default) | proxy-sidecar | |
|---|---|---|
| Image | authbridge-envoy (140 MB) |
authbridge-light (29 MB) |
| Traffic interception | iptables + Envoy | HTTP_PROXY env vars |
| Init container | proxy-init (NET_ADMIN) | None |
| Container name | envoy-proxy |
authbridge-proxy |
| Ollama port exclusion | Required (annotation) | Not needed |
Note: Proxy-sidecar mode requires the agent to read the
PORTenv var. All agents in kagenti/agent-examples support this since v0.1.0-alpha.11.
AuthBridge supports dynamic log-level switching for debugging auth failures without redeploying.
Send SIGUSR1 to the authbridge process using an ephemeral debug container:
POD=$(kubectl get pod -n team1 -l app.kubernetes.io/name=weather-service \
--no-headers | grep Running | head -1 | awk '{print $1}')
# For envoy-sidecar mode:
kubectl debug -n team1 "$POD" \
--image=ghcr.io/kagenti/kagenti-extensions/proxy-init:latest \
--target=envoy-proxy -- kill -USR1 1
# For proxy-sidecar mode:
kubectl debug -n team1 "$POD" \
--image=ghcr.io/kagenti/kagenti-extensions/proxy-init:latest \
--target=authbridge-proxy -- kill -USR1 1Send SIGUSR1 again to toggle back to INFO level.
At DEBUG level, every auth decision logs full context:
- Inbound: request path, expected audience, token audience, scopes, subject
- Outbound: target host, audience, scopes, exchange success/failure details
- Bypass: which paths were skipped
- Cache: hit/miss for token exchange results
Example (Info lines are always visible; Debug lines appear after SIGUSR1 toggle):
level=DEBUG msg="validating inbound JWT" path=/ expectedAudience=spiffe://localtest.me/ns/team1/sa/weather-service
level=INFO msg="inbound authorized" subject=... clientID=kagenti
level=DEBUG msg="inbound authorized details" path=/ audience="[spiffe://...]" scopes="[openid ...]"
level=INFO msg="outbound passthrough" host=weather-tool-mcp.team1.svc.cluster.local:8000 reason="no matching route"
- Go to the Agent Catalog, find
weather-service, and click Delete. - Go to the Tool Catalog, find
weather-tool, and click Delete.
kubectl delete deployment weather-service -n team1
kubectl delete deployment weather-tool -n team1
kubectl delete svc weather-service -n team1
kubectl delete svc weather-tool -n team1
kubectl delete pod test-client -n team1 --ignore-not-foundkubectl delete namespace team1- Advanced Demo: See the GitHub Issue Agent demo for outbound token exchange, scope-based access control, and Alice vs Bob scenarios
- AuthBridge Binary: See the AuthBridge README for inbound JWT validation and outbound token exchange internals
- Multi-Target Demo: See the multi-target demo for route-based token exchange to multiple tool services
- AuthBridge Overview: See the AuthBridge README for architecture details