For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.
Authorization
Verified Code examples on this page have been automatically tested and verified.Control which requests are allowed to reach your backends using authorization policies with Allow, Require, and Deny actions.
Authorization policies in agentgateway let you control which requests are allowed to reach your backends. Policies apply across all traffic types — HTTP routes, LLM providers, MCP servers, and agents — giving you a unified way to enforce access rules.
For a general overview of how policies are structured, see Policy sections.
How authorization works
Agentgateway uses the authorization field inside an AgentgatewayPolicy to evaluate whether an incoming request should be allowed or rejected. Authorization rules are expressed as Common Expression Language (CEL) expressions, which let you match on request headers, JWT claims, source IP addresses, MCP tool names, and more.
The following table lists the authorization fields in an AgentgatewayPolicy:
| Policy section | Field path | Use case |
|---|---|---|
traffic | spec.traffic.authorization | Control access to HTTP routes, LLM backends, or general traffic. |
frontend | spec.frontend.networkAuthorization | Layer 4 network-level authorization on downstream connections (such as source IP filtering). |
backend | spec.backend.authorization | Control access after route selection, using the request for the selected destination backend. |
backend.mcp | spec.backend.mcp.authorization | Control access to specific MCP servers or tools. |
You can also configure backend authorization inline on an AgentgatewayBackend:
| Field path | Scope |
|---|---|
spec.policies.authorization | The whole backend. |
spec.ai.groups[].providers[].policies.authorization | One AI provider in a backend group. |
Backend authorization runs after the destination backend or AI provider is selected. Traffic authorization runs earlier, during traffic policy processing. Both inline fields use the same action and policy.matchExpressions structure as spec.backend.authorization. For an example, see Authorize requests to a selected backend. For inline and attached policy precedence, see Inline AI and authorization policies on a backend.
Note
In standalone deployment mode, the frontend network authorization path is frontendPolicies.networkAuthorization.
Each authorization block contains a single action and a policy with matchExpressions. Because an authorization block takes only one action, a configuration that needs more than one action must be split across multiple AgentgatewayPolicy resources. For an example, see Combine Allow with Require.
Authorization actions
| Action | Behavior |
|---|---|
Allow | Grants access when at least one expression in the policy matches, so multiple expressions are OR’d together. If any Allow rule is configured, requests that match none of them are denied. This is the recommended action for most use cases. |
Require | Grants access only when every expression evaluates to true, so multiple expressions are AND’d together. Use Require to add mandatory conditions that must hold no matter which Allow rules match. See the Require example. |
Deny | Denies access when at least one expression matches, and overrides a matching Allow. See the warning and example below. |
Evaluation order
When AgentgatewayPolicy resources are applied to a Gateway, their authorization rules are combined and evaluated as follows:
Denyrules are evaluated first. If anyDenyexpression matches, the request is denied, even if anAllowrule also matches.Requirerules are evaluated next. If anyRequireexpression isfalse, the request is denied.Allowrules are evaluated last. If at least oneAllowrule is configured, then at least oneAllowexpression must match, or the request is denied.
Note
Step 3 applies only when at least one Allow rule exists. A policy that contains only Require rules allows any request that satisfies all of those rules, because there is no allowlist to check against.
This makes Allow and Require the recommended combination for safe, readable policies.
Note that authorization runs only after authentication succeeds. A request with a missing, malformed, or unverifiable JWT fails JWT authentication and returns a 401 before any authorization expression is evaluated. Authorization denials return a 403.
Setup and test authorization
This section walks you through an end-to-end authorization setup that allows requests from one user and denies requests from another.
Before you begin
Follow the Get started guide to install agentgateway.
Follow the Sample app guide to create a gateway proxy with an HTTP listener and deploy the httpbin sample app.
Get the external address of the gateway and save it in an environment variable.
Tip
Kind cluster? Kind does not support
LoadBalancerservices by default. To use this option with a Kind cluster, install and runcloud-provider-kind.export INGRESS_GW_ADDRESS=$(kubectl get svc -n agentgateway-system agentgateway-proxy -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}") echo $INGRESS_GW_ADDRESS
1. Apply the authorization policy
Apply an AgentgatewayPolicy that validates JWTs and allows only requests from the user alice.
kubectl apply -f- <<EOF
apiVersion: agentgateway.dev/v1alpha1
kind: AgentgatewayPolicy
metadata:
name: authz-guide
namespace: agentgateway-system
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: agentgateway-proxy
traffic:
jwtAuthentication:
mode: Strict
providers:
- issuer: solo.io
jwks:
inline: '{"keys":[{"use":"sig","kty":"RSA","kid":"5891645032159894383","n":"5Zb1l_vtAp7DhKPNbY5qLzHIxDEIm3lpFYhBTiZyGBcnre8Y8RtNAnHpVPKdWohqhbihbVdb6U7m1E0VhLq7CS7k2Ng1LcQtVN3ekaNyk09NHuhl9LCgqXT4pATt6fYTKtZ__tEw4XKt3QqVcw7hV0YaNVC5xXGYVBh5_2-K5aW9u2LQ7FSax0jPhWdoUB3KbOQfWNOA3RwOqYn4gmc9wVToVLv6bXCVhIYWKnAVcX89C00eM7uBHENvOydD14-ZnLb4pzz2VGbU6U65odpw_i4r_mWXvoUgwogXAXp80TsYwMzLHcFo4GVDNkaH0hjuLJCeISPfYtbUJK6fFaZGBw","e":"AQAB"}]}'
authorization:
action: Allow
policy:
matchExpressions:
- "jwt.sub == 'alice'"
EOF| Field | Description |
|---|---|
jwtAuthentication | Validates the JWT on the incoming request. Authorization expressions that reference jwt require this section, because the jwt context exists only after a token is verified. In this example, a local JWKS is provided inline. For more options, see JWT auth. |
authorization.action | The authorization action to take. Use Allow for allowlisting. |
authorization.policy.matchExpressions | A list of CEL expressions. For an Allow action, at least one expression must match (OR logic). For a Require action, all expressions must evaluate to true (AND logic). |
For a full list of available CEL variables you can use in expressions, see the CEL reference.
2. Save the JWT tokens
Save JWT tokens for the users Alice and Bob. Both tokens are signed by the key in the JWKS that you applied in the previous step, so both pass authentication. Only Alice’s token satisfies the authorization rule.
You can optionally create other JWT tokens by using the JWT generator tool. Note that to use JWTs with agentgateway proxies, make sure that the JWTs return Key ID (kid) and expiration date (exp) values in the JWT header.
Save the JWT token for Alice, whose
subclaim isalice.export ALICE_JWT="eyJhbGciOiJSUzI1NiIsImtpZCI6IjU4OTE2NDUwMzIxNTk4OTQzODMiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJzb2xvLmlvIiwic3ViIjoiYWxpY2UiLCJleHAiOjIwNzM2NzA0ODIsIm5iZiI6MTc2NjA4NjQ4MiwiaWF0IjoxNzY2MDg2NDgyfQ.C-KYZsfWwlwRw4cKHXWmjN5bwWD80P0CVYP6-mT5sX6BH3AR1xNrOApPF9X0plwVD4_AsWzVo435j1AmgBzPwIjhHPKtxXycaKEwSEHYFesyi-XCEJtaQZZVcjOJOs-12L2ZJeM_csk9EqKKSx0oj3jj6BciqBnLn6_hK9sEtoGenEVWEdOpkjRQBxk1m-rVZNY2IvxXMuj9C7jGXv_Sn3cU5w6arXWUsdoQtYTl5tmuF15nkD3DnQfLjDyz59FTKXUR_QkhXV81amejrDSTroJ42_RLC9ABXqdMORCe-Hus-f1utLURfAYGvmnEVeYJO8BFhedTR6lFLnVS0u2Fpw"Save the JWT token for Bob, whose
subclaim isbob.export BOB_JWT="eyJhbGciOiJSUzI1NiIsImtpZCI6IjU4OTE2NDUwMzIxNTk4OTQzODMiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJzb2xvLmlvIiwic3ViIjoiYm9iIiwiZXhwIjoyMDczNjcwNDgyLCJuYmYiOjE3NjYwODY0ODIsImlhdCI6MTc2NjA4NjQ4Mn0.ZHAw7nbANhnYvBBknN9_ORCQZ934Vv_vAelx8odC3bsC5Yesif7ZSsnEp9zFjGG6wBvvV3LrtuBuWx9mTYUZS6rwWUKsvDXyheZXYRmXndOqpY0gcJJaulGGqXncQDkmqDA7ZeJLG1s0a6shMXRs6BbV370mYpu8-1dZdtikyVL3pC27QNei35JhfqdYuMw1fMptTVzypx437l9j2htxqtIVgdWUc1iKD9kNKpkJ5O6SNbi6xm267jZ3V_Ns75p_UjLq7krQIUl1W0mB0ywzosFkrRcyXsBsljXec468hgHEARW2lec8FEe-i6uqRuVkFD-AeXMfPhXzqdwysjG_og"
3. Verify the policy
Send a request without a JWT. The request fails JWT authentication before authorization runs, so you get back a
401 Unauthorizedresponse.curl -i http://$INGRESS_GW_ADDRESS:80/headers -H "host: www.example.com"Example output:
HTTP/1.1 401 UnauthorizedSend a request with Bob’s JWT. The token is valid, so authentication succeeds, but the
subclaim does not match theAllowexpression. You get back a403 Forbiddenresponse.curl -i http://$INGRESS_GW_ADDRESS:80/headers -H "host: www.example.com" \ -H "Authorization: Bearer $BOB_JWT"Example output:
HTTP/1.1 403 ForbiddenSend a request with Alice’s JWT. The token is valid and the
subclaim matches theAllowexpression, so the request succeeds.curl -i http://$INGRESS_GW_ADDRESS:80/headers -H "host: www.example.com" \ -H "Authorization: Bearer $ALICE_JWT"Example output:
HTTP/1.1 200 OK
More examples
Authorize requests to a selected backend
Complete Setup and test authorization first, and keep the Gateway’s JWT and traffic authorization policy in place. The following backend policy allows only GET requests to the httpbin Service. The Gateway’s traffic policy still requires Alice’s JWT. After agentgateway selects httpbin as the destination, the backend policy also checks the request method.
Create a policy in the same namespace as the httpbin Service that it targets.
kubectl apply -f- <<EOF apiVersion: agentgateway.dev/v1alpha1 kind: AgentgatewayPolicy metadata: name: httpbin-backend-authz namespace: httpbin spec: targetRefs: - group: "" kind: Service name: httpbin backend: authorization: action: Allow policy: matchExpressions: - "request.method == 'GET'" EOFField Description spec.targetRefsTargets the httpbin Service in the policy’s namespace. The policy applies when a route selects that Service as its backend. spec.backend.authorization.actionAllowrequires at least one expression to match.spec.backend.authorization.policy.matchExpressionsAllows only requests whose HTTP method is GET.Send a
GETand aPOSTrequest with Alice’s JWT. TheGETrequest satisfies both policies and returns200. ThePOSTrequest passes the Gateway’s JWT and traffic authorization checks, but the backend policy denies it with403.curl -s -o /dev/null -w '%{http_code}\n' \ "http://${INGRESS_GW_ADDRESS}:80/headers" \ -H 'host: www.example.com' -H "Authorization: Bearer ${ALICE_JWT}" curl -s -o /dev/null -w '%{http_code}\n' -X POST \ "http://${INGRESS_GW_ADDRESS}:80/headers" \ -H 'host: www.example.com' -H "Authorization: Bearer ${ALICE_JWT}"Example output:
200 403Delete the backend policy when you finish testing. The Gateway’s JWT and traffic authorization policy continues to apply.
kubectl delete AgentgatewayPolicy httpbin-backend-authz -n httpbin
Combine Allow with Require
An authorization block takes a single
action, so to combine anAllowrule with aRequirerule, create two AgentgatewayPolicy resources that target the same Gateway. Their rules are combined, and a request must satisfy both.The following policy adds a
Requirerule to theAllowpolicy from the previous section. TheRequirerule enforces an internal-traffic header on every request, no matter whichAllowrules are defined.kubectl apply -f- <<EOF apiVersion: agentgateway.dev/v1alpha1 kind: AgentgatewayPolicy metadata: name: authz-require namespace: agentgateway-system spec: targetRefs: - group: gateway.networking.k8s.io kind: Gateway name: agentgateway-proxy traffic: authorization: action: Require policy: matchExpressions: - "request.headers['x-internal'] == 'true'" EOFWith both policies applied, only Alice’s requests that also carry the
x-internal: trueheader succeed.Request Allow(jwt.sub == 'alice')Require(x-internal: true)Result Alice’s JWT, with the header Matches Satisfied 200Alice’s JWT, no header Matches Not satisfied 403Bob’s JWT, with the header No match Satisfied 403Bob’s JWT, no header No match Not satisfied 403Tip
Use
Requirewhen you need to enforce a mandatory condition across all traffic, such as requiring an internal header, a valid JWT group claim, or a specific source address range. Because a failedRequirecheck always denies the request,Requirecannot be bypassed by adding anotherAllowrule.Deny policies
Warning
The
Denyaction is available but is not recommended for most use cases.Denyrules are error-prone because they often require double-negative logic. For example, to block traffic from outside a valid group, you might attempt to write an expression likejwt.group != 'eng'. However, if the JWT does not contain agroupclaim at all, this expression evaluates tofalseand the rule does not fire — silently allowing requests you intended to block.Use the
Requireaction instead.Requireinverts the logic so you write positive conditions:action: Requirewithjwt.group == 'eng'. This is clearer and safer. If you must test whether a claim is present, use thehas()function, such ashas(jwt.group) && jwt.group == 'eng'. For more information, see the CEL variables reference.If you must use
Deny, prefer expressions over request attributes that are always present, such as the request path. The following example blocks access to the/adminpath for every client, including clients that a separateAllowrule permits.kubectl apply -f- <<EOF apiVersion: agentgateway.dev/v1alpha1 kind: AgentgatewayPolicy metadata: name: authz-deny namespace: agentgateway-system spec: targetRefs: - group: gateway.networking.k8s.io kind: Gateway name: agentgateway-proxy traffic: authorization: action: Deny policy: matchExpressions: - "request.path.startsWith('/admin')" EOFRestrict access by source address
The
source.addressvariable is an IP-typed value, not a string, so you cannot compare it to a CIDR string directly. Use thecidr()function withcontainsIP()instead, as shown in the followingRequirerule.traffic: authorization: action: Require policy: matchExpressions: - "cidr('10.0.0.0/8').containsIP(source.address)"Warning
source.addressis the peer address of the connection as the gateway observes it, which is not always the original client address. If the connection passes through a cloud load balancer or another proxy, or if source IP preservation is not configured,source.addressis that intermediate address. Verify which address your gateway actually sees before you rely on it, because aRequirerule that never matches denies every request.For Layer 4 network-level filtering on downstream connections, use
spec.frontend.networkAuthorizationinstead.Restrict network access by TLS SNI
In a
spec.frontend.networkAuthorizationpolicy, use thedestination.hostnamevariable to admit only TLS connections for a specific Server Name Indication (SNI) hostname.Create a self-signed TLS certificate for two hostnames,
db.internal.example.comandother.internal.example.com, and store it in a Kubernetes secret.mkdir -p example_certs openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 \ -subj "/CN=db.internal.example.com" \ -addext "subjectAltName=DNS:db.internal.example.com,DNS:other.internal.example.com" \ -keyout example_certs/internal.example.com.key -out example_certs/internal.example.com.crt kubectl create secret tls tls-sni-cert -n agentgateway-system \ --key example_certs/internal.example.com.key --cert example_certs/internal.example.com.crtCreate a Gateway with a TLS listener that terminates TLS, and a TLSRoute that forwards the decrypted traffic to the httpbin sample app. agentgateway sets
destination.hostnameonly on listeners withprotocol: TLS. On HTTP and HTTPS listeners, the variable is always unset, even when the client sends SNI.kubectl apply -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: tls-sni namespace: agentgateway-system spec: gatewayClassName: agentgateway listeners: - name: tls protocol: TLS port: 8443 tls: mode: Terminate certificateRefs: - name: tls-sni-cert kind: Secret allowedRoutes: namespaces: from: All --- apiVersion: gateway.networking.k8s.io/v1 kind: TLSRoute metadata: name: tls-sni namespace: httpbin spec: parentRefs: - name: tls-sni namespace: agentgateway-system hostnames: - "*.internal.example.com" rules: - backendRefs: - name: httpbin port: 8000 EOFApply a network authorization policy to the Gateway that requires the
db.internal.example.comSNI hostname.destination.hostnameis unset when a client sends no SNI, and aRequireexpression that references an unset variable never matches, so the policy denies every connection without SNI. The policy also denies every connection to an HTTP or HTTPS listener on the Gateway that you target, so target only a Gateway whose listeners useprotocol: TLS.kubectl apply -f- <<EOF apiVersion: agentgateway.dev/v1alpha1 kind: AgentgatewayPolicy metadata: name: tls-sni namespace: agentgateway-system spec: targetRefs: - group: gateway.networking.k8s.io kind: Gateway name: tls-sni frontend: networkAuthorization: action: Require policy: matchExpressions: - "destination.hostname == 'db.internal.example.com'" EOFPort-forward the Gateway on port 8443.
kubectl port-forward deployment/tls-sni -n agentgateway-system 8443:8443In another terminal, send a request with each SNI hostname. The
--http1.1option is required because agentgateway forwards the decrypted TCP stream to httpbin as is, so the client must not negotiate HTTP/2 in the TLS handshake.db.internal.example.com: The connection is admitted and httpbin returns a 200 HTTP response code.curl -s -o /dev/null -w '%{http_code}\n' --http1.1 \ --cacert example_certs/internal.example.com.crt \ --resolve db.internal.example.com:8443:127.0.0.1 \ https://db.internal.example.com:8443/headersExample output:
200other.internal.example.com: The TLSRoute matches the hostname, but the policy rejects the connection before any data is proxied.curl -s -o /dev/null -w '%{http_code}\n' --http1.1 \ --cacert example_certs/internal.example.com.crt \ --resolve other.internal.example.com:8443:127.0.0.1 \ https://other.internal.example.com:8443/headersExample output:
000The proxy logs record the denial.
kubectl logs deployment/tls-sni -n agentgateway-system | grep "authorization failed"Example output:
2026-09-24T16:15:16.736075Z error request src.addr=10.244.0.1:54721 tls.sni=other.internal.example.com protocol=tcp error="authorization failed" duration=0ms
Stop the port-forward, and delete the resources that you created.
kubectl delete AgentgatewayPolicy tls-sni -n agentgateway-system kubectl delete tlsroute tls-sni -n httpbin kubectl delete gateway tls-sni -n agentgateway-system kubectl delete secret tls-sni-cert -n agentgateway-system
MCP authorization
You can apply authorization policies specifically to MCP servers using the
spec.backend.mcp.authorizationfield in an AgentgatewayPolicy. This lets you control which clients or JWT token holders can access specific MCP tools.For a complete guide with examples, see JWT auth for MCP services.
JWT authorization
To use JWT claims in authorization policies, you first need to configure JWT authentication in the same policy using
spec.traffic.jwtAuthentication, as shown in Setup and test authorization. Without it, thejwtcontext does not exist, every expression that referencesjwtfails to match, and the policy denies all traffic while still reporting as accepted and attached.After the gateway validates a JWT, the decoded claims are available in CEL expressions as top-level fields on
jwt, such asjwt.suborjwt.group.For setup instructions and examples, see JWT auth.
For a list of available JWT variables, see the CEL variables reference.
Cleanup
You can remove the resources that you created in this guide.kubectl delete AgentgatewayPolicy authz-guide -n agentgateway-system kubectl delete AgentgatewayPolicy authz-require -n agentgateway-system --ignore-not-found kubectl delete AgentgatewayPolicy authz-deny -n agentgateway-system --ignore-not-found