---
title: "Envoy ext_authz: enforcing one authorization policy at the gateway and in the service"
description: "Envoy external authorization with the ext_authz filter lets the gateway and the service run checks against one policy set. This guide covers the CheckRequest to Cerbos mapping in CEL, policy outputs as request headers, embedded versus external PDP, bypass paths, caching trade offs and what AuthZEN leaves undefined."
author: "Alex Olivier"
date: "2026-08-14T14:58:16.217Z"
canonical: "https://www.cerbos.dev/blog/envoy-ext-authz-enforcing-one-authorization-policy-at-gateway-in-service"
image: "https://stylish-appliance-1c1cc1c30d.media.strapiapp.com/Envoy_ext_authz_enforcing_one_authorization_policy_at_the_gateway_and_in_the_service_5daf3f2011.png"
tags: ["guide","engineering"]
source: "https://www.cerbos.dev/blog/envoy-ext-authz-enforcing-one-authorization-policy-at-gateway-in-service"
---

# Envoy ext_authz: enforcing one authorization policy at the gateway and in the service

Most stacks end up running two access control systems that were never designed together. The gateway holds one set of rules, expressed as route matchers, token scope requirements and proxy filter config. The service holds another in application code, checking whether this principal owns this record and whether the record is in a state they may see. Both were correct on the day they were written.

Then they drift. A route ships and the gateway gets an entry the service check never learns about. A service tightens a rule and the gateway keeps waving requests through to a 403 the platform team has to explain. After a year, answering who can do what means reading a proxy config, reading a codebase, and reconciling them by hand. This is the coupling problem that pushed teams toward [externalized authorization](https://www.cerbos.dev/blog/why-external-authorization) in the first place, reintroduced at the network boundary.

The enforcement point is not the missing piece. [Envoy](https://www.cerbos.dev/ecosystem/cerbos-envoy) has shipped external authorization as a first-class HTTP filter for years, and the industry now has a standardized version of the same idea. The OpenID Foundation published the [Authorization API 1.0](https://openid.net/specs/authorization-api-1_0.html) as a final specification in January 2026, defining how any compliant enforcement point asks any compliant decision point a question. What it deliberately leaves open is everything behind that decision point. Policy language, internal architecture and state management are all out of scope, and it assumes the caller passes in the attributes an evaluation needs. The interface is settled. How the decision point gets those facts is not, and that is what every integration still has to answer.

## The gateway is already able to delegate

The ext\_authz filter sits in the HTTP filter chain ahead of the router. For every matching request Envoy pauses and sends a gRPC CheckRequest to a configured cluster, carrying the path, method, headers and peer metadata. On allow, Envoy can attach headers before forwarding upstream. On deny, it returns a status of the authorization service's choosing and the request never reaches the backend. The filter can also be disabled per route, so health checks and other unauthenticated paths skip the call entirely.

What the filter must not become is a place where rules live. [NIST SP 800-162](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-162.pdf) draws the boundary the way every serious reference architecture does, splitting an access control mechanism into a decision point, an enforcement point that carries out what it is told, and a context handler that assembles the attributes the decision needs. The enforcement point intercepts, asks and enforces. It holds no logic of its own.

Most gateway integrations stop short of that, pointing the filter at something that only understands routes, so the service keeps running its own [role based](https://www.cerbos.dev/blog/what-is-role-based-access-control-and-when-to-use-it) checks against a separate rule set and the drift starts again.

## One address answers both questions

[Cerbos Synapse](https://www.cerbos.dev/product-cerbos-synapse) implements the Envoy external authorization protocol natively. Envoy's ext\_authz cluster points at it over gRPC, and the same listener serves the standard Cerbos API that application SDKs speak. That detail closes the gap. The gateway and the service call the same address, run their checks through the same engine, and read the same policy bundle.

Because the protocol support is built in, the integration is configuration rather than an adapter binary you build, ship and keep current with two upstreams at once. Synapse can embed a Cerbos PDP in the same process, so the decision does not fan out to a second hop, and it can point at an existing PDP fleet instead. Read against the NIST decomposition it is the context handler, the piece that assembles what the decision needs, which is precisely the role the standards leave undefined.

## The mapping turns an HTTP request into a policy question

Envoy's CheckRequest is a network structure. A Cerbos check is a question about a principal, a resource and an action. Synapse bridges the two with CEL expressions in configuration, and the shape of that mapping determines what your policies can reason about.

A typical mapping pulls the bearer token from the authorization header, decodes the payload, and takes the subject and roles claims as the principal. The resource id is the request path, with path and method carried as attributes the policy can test. The action is a single fixed verb, because at the edge there is only one thing being asked. The raw token goes through as auxiliary data, so the decision point verifies the signature rather than the gateway or the service implementing that logic.

The response mapping runs the other way. An allow becomes an OK status, a deny becomes PERMISSION\_DENIED with a chosen status and body. More usefully, policy outputs become request headers. When the gateway rule fires it emits the resolved principal id, roles and account identifier, and Envoy attaches those before forwarding. The decision does not only gate the request, it carries context forward, and a new output adds a header without touching proxy config.

This is the real design choice in any enforcement point. It can send only the instant attributes it alone knows, such as path, headers and caller address. It can send fully qualified requests with every derived attribute resolved up front. Or it can send minimal identifiers and let the decision layer resolve the rest, which is where a Synapse data source pulls current attributes from an identity provider or a database. Most deployments mix all three. The companion piece on identity attributes at decision time argues for resolving them late rather than trusting a token issued hours ago.

## The service asks a narrower question against the same policy set

Past the gateway, the API has the request context it needs in headers and does its own check. It scopes its query to the requested account, builds a batch of document resources carrying status and owning account, and asks the same endpoint which of them this principal may read. Regular users get published documents from their own account, administrators get everything.

Both checks are [resource policies](https://docs.cerbos.dev/cerbos/latest/policies/resource_policies.html) in the same bundle. One describes routes, the other documents. They share a principal vocabulary and are reviewed and versioned together. The gateway policy enforces the account boundary so a cross tenant request is refused before it touches application code, and the document policy handles record state, which the edge cannot know. The batching here is the same pattern used to resolve a whole screen of permissions in one call.

## What this changes about the architecture

Route level rules stop living in proxy configuration. They become policy, written in the same language and diffable against the application rules they sit next to. An auditor asking which roles can reach the admin route reads a policy file rather than a filter chain. With [Cerbos Hub](https://www.cerbos.dev/product-cerbos-hub) handling storage and versioning, a change lands at both layers at once, so there is no window where the gateway enforces last week's rules while the service enforces this week's. Both layers emit [decision logs](https://www.cerbos.dev/blog/why-audit-logs-are-important) into one stream, and the mapping carries Envoy's request id into the check, so edge and record level decisions join on one identifier rather than by timestamp.

This shape is not a local preference. Germany's federal API authorization infrastructure, approved by its IT Planning Council on [17 June 2026](https://www.it-planungsrat.de/beschluss/b-2026-16-it) and published open source with twenty three [architecture decision records](https://gitlab.opencode.de/sachsen-anhalt/mid/foederale-api-autorisierungsinfrastruktur), is built on the same split. The [analysis](https://apievangelist.com/2026/07/16/germany-built-the-api-authorization-blueprint-the-rest-of-government-needs/) of the blueprint states it plainly. "Coarse-grained permissions are governed centrally; fine-grained decisions stay with the domains that own the data; and the whole thing is designed so that a decision point keeps working even when the central plane is down."

Its founding thesis is profile, don't invent. The standards already exist, so they get composed rather than reinvented per service, and what gets composed includes OAuth 2.1, FAPI 2.0, DPoP and OpenID AuthZEN between the enforcement and decision points, chosen once for the whole federation.

## Limits and costs worth stating plainly

The edge check only knows what is in the request. Path, method, headers, token claims. It cannot know whether a document is archived, whether an account is suspended, or whether a record has an owner. Anything depending on resource state belongs in the service, and pulling that state into the edge to make the edge smarter recreates the coupling this pattern removes.

The structural limit is larger than granularity. A gateway only enforces what routes through it. Being almost entirely configuration driven is its strength and its weakness, because any path reaching the service without passing the filter goes unauthorized. Internal callers, batch jobs and anything resolving the service by its cluster address bypass the edge completely. The service side check is not belt and braces. It is the part that survives a topology change.

The same argument arrives from the agent direction. CoSAI's [agentic identity](https://github.com/cosai-oasis/ws4-secure-design-agentic-systems) guidance, approved in March 2026, requires enforcement at agents, tools, APIs and data systems rather than only at perimeter gateways, because front door enforcement alone produces confused deputy failures as tokens propagate down a chain of calls. A gateway answers whether a caller may reach a route, not what an agent several hops in should do with what it found there.

Enrichment carries its own cost. Once a decision point resolves attributes from an identity provider or a database, that source joins the request path, and centralizing every attribute can move the bottleneck from authorization to the data layer. Caching answers the latency question and opens a correctness one, because a cached attribute is a claim about the past. Cache lifetime is a decision about how stale an authorization input may be, not a tuning parameter.

Then the plain latency cost. Every matched request waits on a gRPC round trip before routing. An embedded decision point removes the second hop and Envoy holds a multiplexed HTTP/2 connection, so there is no per request setup. Fail closed costs availability during an outage, fail open costs security, and that choice should be made on purpose rather than inherited.

This pattern extends to a mesh without being identical to one. Running the same checks on sidecars for east to west traffic brings workload identity in, which the [Istio](https://www.cerbos.dev/ecosystem/cerbos-istio) piece covers. The full mapping reference is in the [Envoy extension](https://docs.cerbos.dev/synapse/latest/extensions/envoy-extension) documentation.

## The boundary is drawn by data, not by ownership

The useful framing is not that authorization moved to the gateway. It is that the gateway and the service ask different questions of the same policy set, and the line between them is drawn by what data is available at each point rather than by which team owns the rules. The edge answers what it can from the request. The service answers what needs the record in hand. Neither keeps a private copy of the logic.

The standards settled how the question gets asked. Assembling the facts behind it stays an architecture decision, and it is the one worth the time. The mapping fields and deployment options are in the [Synapse documentation](https://docs.cerbos.dev/synapse/latest/), and the [launch post](https://www.cerbos.dev/blog/introducing-cerbos-synapse-unified-authorization-context-and-enforcement-across-your-stack) covers the other protocols the same mechanism reaches.

[**Try Cerbos**](https://hub.cerbos.cloud) to see how this works in practice, or [**book a call**](https://www.cerbos.dev/workshop) to talk through your architecture with the team.

**Go deeper:** [How to adopt externalized authorization](https://solutions.cerbos.dev/how-to-adopt-externalized-authorization) (eBook) for a structured approach to moving authorization out of application code and into a policy layer

## FAQ

### What is Envoy external authorization (ext_authz)?

Envoy external authorization is a first class HTTP filter, ext\_authz, that sits in the filter chain ahead of the router. For every matching request Envoy pauses and sends a gRPC CheckRequest to a configured authorization service carrying the path, method, headers and peer metadata. On allow, Envoy can attach headers before forwarding upstream. On deny, the request never reaches the backend, and the filter can be disabled per route so health checks and other unauthenticated paths skip the call.

### How does Envoy ext\_authz work with Cerbos?

Envoy ext\_authz works with Cerbos through [Cerbos Synapse](https://www.cerbos.dev/product-cerbos-synapse), which implements the Envoy external authorization protocol natively. Envoy's ext\_authz cluster points at Synapse over gRPC, and the same listener serves the standard Cerbos API that application SDKs speak, so the gateway and the service call one address and read one policy bundle. Because the protocol support is built in, the integration is configuration rather than an adapter binary you build and keep current with two upstreams. The full field mapping is in the [Envoy extension documentation](https://docs.cerbos.dev/synapse/latest/extensions/envoy-extension).

### Should authorization live in the API gateway or in the service? 

Authorization should live in both the API gateway and the service, split by what data each one can see rather than by which team owns the rules. The gateway answers what it can from the request, so a cross tenant call is refused before it touches application code. The service answers what needs the record in hand, such as whether a document is published or an account is suspended. Neither layer keeps a private copy of the logic, which is the point of [externalized authorization](https://www.cerbos.dev/blog/externalized-authorization-management-eam-and-benefits) in the first place.

### Can an API gateway handle fine grained authorization on its own?

An API gateway cannot handle fine grained authorization on its own, for two reasons. The edge check only knows what is in the request, so path, method, headers and token claims, and it cannot know resource state such as whether a record is archived or has an owner. The larger limit is that a gateway only enforces what routes through it, so internal callers, batch jobs and anything resolving the service by its cluster address bypass it completely. That is why the [service side check](https://www.cerbos.dev/blog/service-to-service-authorization) is not belt and braces, it is the part that survives a topology change.

### How do you map an Envoy CheckRequest to a Cerbos policy check?

You map an Envoy CheckRequest to a Cerbos policy check with CEL expressions in configuration. A typical mapping pulls the bearer token from the authorization header, decodes the payload and takes the subject and roles claims as the principal, uses the request path as the resource id with path and method as attributes, and sets a single fixed action because the edge is only ever asking one thing. The raw token goes through as auxiliary data so the decision point verifies the signature. Running the other way, policy outputs become request headers, so a new output adds a header without touching proxy config.
