Route requests to an AWS Bedrock AgentCore agent through Solo Agent Gateway — no proxy, no custom code, no AWS SDK. The gateway handles authentication to AgentCore using a Cognito JWT stored in a Kubernetes Secret, so clients don't need any AWS credentials or tokens.
your auth (optional) Cognito JWT (from K8s Secret)
API key, OAuth, none backend.auth.secretRef
│ │
▼ ▼
┌──────────┐ ┌──────────────────┐ ┌─────────────────────┐
│ │ │ │ │ │
│ Client ├─────▶│ Agent Gateway ├─────▶│ AWS Bedrock │
│ │◀─────┤ │◀─────┤ AgentCore │
│ │ │ │ │ │
└──────────┘ └──────────────────┘ └─────────────────────┘
├ path rewrite │ ├ customJWTAuthorizer│
├ host rewrite │ ├ validates Cognito │
├ TLS origination │ │ ID token (aud) │
├ rate limiting │ ├ invokes agent │
└ CORS, metrics ┘ └────────────────────┘
│
┌─────┴─────┐
│ K8s │
│ Secret │
│ (JWT) │
└───────────┘
The client-to-gateway leg uses whatever auth you choose (API keys, OAuth, or none). The gateway-to-AgentCore leg is handled automatically: Agent Gateway reads a Cognito JWT from a Kubernetes Secret and attaches it to every upstream request. AgentCore validates the token and invokes the agent.
- An EKS cluster with Solo Enterprise Agent Gateway installed
kubectlandawsCLI tools configured- An AgentCore agent runtime deployed (you need the ARN)
python3,jq, andcurlinstalled
export AGENT_RUNTIME_ARN="arn:aws:bedrock-agentcore:us-east-1:986112284769:runtime/rvennam_agent-tEJ8OxEBo1"
export AWS_REGION="us-east-1"
export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# URL-encode the ARN for the HTTP path
export ENCODED_ARN=$(python3 -c "from urllib.parse import quote; print(quote('${AGENT_RUNTIME_ARN}', safe=''))")
# Agent Gateway
export GATEWAY_NAME="agentgateway"
export GATEWAY_NS="enterprise-agentgateway"
export GW_ADDR=$(kubectl get svc -n $GATEWAY_NS $GATEWAY_NAME \
-o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}")
export GW_PORT=$(kubectl get svc -n $GATEWAY_NS $GATEWAY_NAME \
-o jsonpath="{.spec.ports[0].port}")
echo "Gateway: http://${GW_ADDR}:${GW_PORT}"Create a User Pool with an app client and a service user. The service user's ID token will be used for backend authentication to AgentCore.
# Create the User Pool
export POOL_ID=$(aws cognito-idp create-user-pool \
--pool-name agentcore-workshop \
--region $AWS_REGION \
--query 'UserPool.Id' --output text)
echo "Pool ID: $POOL_ID"
# Create an app client with password auth enabled
export CLIENT_RESPONSE=$(aws cognito-idp create-user-pool-client \
--user-pool-id $POOL_ID \
--client-name agentcore-client \
--generate-secret \
--explicit-auth-flows ALLOW_ADMIN_USER_PASSWORD_AUTH ALLOW_REFRESH_TOKEN_AUTH \
--region $AWS_REGION)
export COGNITO_CLIENT_ID=$(echo $CLIENT_RESPONSE | jq -r '.UserPoolClient.ClientId')
export COGNITO_CLIENT_SECRET=$(echo $CLIENT_RESPONSE | jq -r '.UserPoolClient.ClientSecret')
echo "Client ID: $COGNITO_CLIENT_ID"
# Create a service user for gateway-to-AgentCore authentication
aws cognito-idp admin-create-user \
--user-pool-id $POOL_ID \
--username gateway-service \
--message-action SUPPRESS \
--region $AWS_REGION
aws cognito-idp admin-set-user-password \
--user-pool-id $POOL_ID \
--username gateway-service \
--password 'GatewayService1!' \
--permanent \
--region $AWS_REGION
echo "Created service user: gateway-service"Cognito ID tokens include the aud claim (set to the client ID), which AgentCore's JWT authorizer requires.
# Compute the SECRET_HASH required by Cognito
export SECRET_HASH=$(python3 -c "
import hmac, hashlib, base64
msg = 'gateway-service' + '${COGNITO_CLIENT_ID}'
h = hmac.new('${COGNITO_CLIENT_SECRET}'.encode(), msg.encode(), hashlib.sha256)
print(base64.b64encode(h.digest()).decode())
")
# Authenticate and get the ID token
export TOKEN=$(aws cognito-idp admin-initiate-auth \
--user-pool-id $POOL_ID \
--client-id $COGNITO_CLIENT_ID \
--auth-flow ADMIN_USER_PASSWORD_AUTH \
--auth-parameters USERNAME=gateway-service,PASSWORD='GatewayService1!',SECRET_HASH=$SECRET_HASH \
--region $AWS_REGION \
--query 'AuthenticationResult.IdToken' --output text)
echo "Token: ${TOKEN:0:20}..."Point AgentCore at the Cognito User Pool's OIDC discovery endpoint. Set allowedAudience to the Cognito client ID (which matches the aud claim in ID tokens).
export COGNITO_ISSUER="https://cognito-idp.${AWS_REGION}.amazonaws.com/${POOL_ID}"
aws bedrock-agentcore update-agent-runtime \
--agent-runtime-id $(echo $AGENT_RUNTIME_ARN | awk -F'/' '{print $2}') \
--authorizer-configuration '{
"customJWTAuthorizer": {
"discoveryUrl": "'${COGNITO_ISSUER}'/.well-known/openid-configuration",
"allowedAudience": ["'${COGNITO_CLIENT_ID}'"]
}
}' \
--region $AWS_REGIONVerify:
aws bedrock-agentcore get-agent-runtime \
--agent-runtime-id $(echo $AGENT_RUNTIME_ARN | awk -F'/' '{print $2}') \
--region $AWS_REGION \
--query 'authorizerConfiguration'Store the Cognito ID token in a Secret that Agent Gateway will use for backend authentication.
kubectl create ns agentcore
kubectl create secret generic agentcore-jwt \
-n agentcore \
--from-literal=Authorization="Bearer ${TOKEN}"Note: Cognito ID tokens expire after 1 hour. For this workshop, recreate the Secret when the token expires.
For production, the preferred approach is
backend.auth.aws— SigV4 signing with IRSA, which eliminates tokens entirely. This requires agentgateway-enterprise#126 (configurable SigV4 service name) to land. Once available, the config simplifies to:backend: tls: {} auth: aws: implicit: service: bedrock-agentcoreNo Cognito, no tokens, no rotation — IRSA handles credentials automatically.
Three resources: an external backend, a TLS + backend auth policy, and an HTTPRoute.
kubectl apply -f - <<EOF
---
# External backend pointing to AgentCore
apiVersion: agentgateway.dev/v1alpha1
kind: AgentgatewayBackend
metadata:
name: agentcore
namespace: agentcore
spec:
static:
host: bedrock-agentcore.${AWS_REGION}.amazonaws.com
port: 443
---
# TLS origination + backend authentication
apiVersion: enterpriseagentgateway.solo.io/v1alpha1
kind: EnterpriseAgentgatewayPolicy
metadata:
name: agentcore-backend
namespace: agentcore
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: agentcore
backend:
tls: {}
auth:
secretRef:
name: agentcore-jwt
---
# Route with path + host rewrite
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: agentcore
namespace: agentcore
spec:
parentRefs:
- name: ${GATEWAY_NAME}
namespace: ${GATEWAY_NS}
rules:
- matches:
- path:
type: PathPrefix
value: /
filters:
- type: URLRewrite
urlRewrite:
hostname: bedrock-agentcore.${AWS_REGION}.amazonaws.com
path:
type: ReplaceFullPath
replaceFullPath: /runtimes/${ENCODED_ARN}/invocations
backendRefs:
- name: agentcore
kind: AgentgatewayBackend
group: agentgateway.dev
EOFThe backend.auth.secretRef tells Agent Gateway to read the Authorization key from the agentcore-jwt Secret and attach it as the Authorization header on every request to AgentCore. The client never sees or handles the JWT.
Verify:
kubectl get agentgatewaybackend agentcore -n agentcore \
-o jsonpath='{.status.conditions[0].reason}'; echo
kubectl get httproute agentcore -n agentcore \
-o jsonpath='{.status.parents[0].conditions[0].reason}'; echoBoth should show Accepted.
curl -s -X POST "http://${GW_ADDR}:${GW_PORT}/" \
-H "Content-Type: application/json" \
-d '{"role":"user","content":[{"text":"Hello from Agent Gateway!"}]}' | jq .The client sends a plain request — no Authorization header. Agent Gateway attaches the Cognito JWT from the Secret and forwards to AgentCore over TLS.
To confirm that authentication is handled by the gateway (not the client), send a request directly to AgentCore without a token:
# Direct to AgentCore (bypassing gateway) — expect 401
curl -s -o /dev/null -w "HTTP %{http_code}\n" \
-X POST "https://bedrock-agentcore.${AWS_REGION}.amazonaws.com/runtimes/${ENCODED_ARN}/invocations" \
-H "Content-Type: application/json" \
-d '{"role":"user","content":[{"text":"test"}]}'AgentCore rejects the direct request (no JWT), but the same request through the gateway succeeds because the gateway attaches the JWT.
kubectl apply -f - <<EOF
apiVersion: ratelimit.solo.io/v1alpha1
kind: RateLimitConfig
metadata:
name: agentcore-rate-limit
namespace: ${GATEWAY_NS}
spec:
raw:
descriptors:
- key: generic_key
value: counter
rateLimit:
requestsPerUnit: 3
unit: MINUTE
rateLimits:
- actions:
- genericKey:
descriptorValue: counter
type: REQUEST
---
apiVersion: enterpriseagentgateway.solo.io/v1alpha1
kind: EnterpriseAgentgatewayPolicy
metadata:
name: agentcore-rate-limit
namespace: agentcore
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: agentcore
traffic:
entRateLimit:
global:
rateLimitConfigRefs:
- name: agentcore-rate-limit
namespace: ${GATEWAY_NS}
EOFTest (first 3 succeed, then 429):
for i in $(seq 1 6); do
CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "http://${GW_ADDR}:${GW_PORT}/" \
-H "Content-Type: application/json" \
-d '{"role":"user","content":[{"text":"ping"}]}')
echo "Request $i: HTTP $CODE"
donekubectl apply -f - <<EOF
apiVersion: enterpriseagentgateway.solo.io/v1alpha1
kind: EnterpriseAgentgatewayPolicy
metadata:
name: agentcore-cors
namespace: agentcore
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: agentcore
traffic:
cors:
allowOrigins:
- exact: "https://your-app.example.com"
allowMethods:
- POST
- OPTIONS
allowHeaders:
- Content-Type
maxAge: 86400s
EOFAgent Gateway exports OTel metrics and traces for all routed traffic.
# Gateway proxy logs
kubectl logs -n ${GATEWAY_NS} -l app.kubernetes.io/name=agentgateway -f
# Grafana (if OTel stack is deployed)
kubectl port-forward svc/grafana-prometheus-grafana -n monitoring 3000:80 &# Kubernetes resources
kubectl delete ns agentcore
kubectl delete ratelimitconfig agentcore-rate-limit -n ${GATEWAY_NS} 2>/dev/null
# Cognito
aws cognito-idp delete-user-pool \
--user-pool-id $POOL_ID \
--region $AWS_REGION| What | How |
|---|---|
| Identity provider | Amazon Cognito User Pool (service user) |
| Backend auth | EnterpriseAgentgatewayPolicy with backend.auth.secretRef |
| Token validation | AgentCore customJWTAuthorizer validates Cognito ID tokens |
| Client auth | None required (gateway handles backend auth) |
| Routing | AgentgatewayBackend + HTTPRoute (path/host rewrite) |
| TLS | EnterpriseAgentgatewayPolicy with backend.tls |
| Custom code | None |
| Docker image | None |
| AWS SDK / IAM roles | None (no boto3, no IRSA) |
| Rate limiting | RateLimitConfig + EnterpriseAgentgatewayPolicy |
| CORS | EnterpriseAgentgatewayPolicy with traffic.cors |
| Observability | Built-in OTel metrics/traces |
This is amazing. Thanks for sharing @rvennam
To round out this example... so we attach the user token to a secret and auto inject it from agw. This is great for demo. But I want to point out what might happen in a real scenario.
Either
the agent is invoked from some application (mobile, web, desktop, etc). maybe over A2A but it doesn't really matter. the application will handle auth with the user. that is, the application will have the user's identity and JWT. so it's possible the app sends this JWT/ID assertion to agent gateway (agw). In that case, agw can passthrough the token to the agent 1
It's possible the token that the app has from login (previous flow) has incorrect audience to just passthrough the gateway. so the gateway could perform some token exchange / obo flow to re-scope the token. and then send it to AgentCore agent. 2