When to delegate access decisions to your own auth service with forward-auth
Use forward-auth when the decision to allow or deny each request must live in an authorization service your organization already owns, such as an internal policy engine or an access service that your security team maintains, while APISIX stays the enforcement point at the edge. The forward-auth plugin sends each incoming request to that external service, which replies with an allow or deny result. By default the plugin issues a GET request to the authorization service, and you can set it to POST when the service needs the request body to decide. The plugin adds X-Forwarded headers so the service can see the original method, host, URI and client address, and it passes selected response headers from the authorization service on to the upstream or the client.
Most teams do not need this plugin. The built-in key-auth, jwt-auth and openid-connect plugins cover many cases with less moving code, and their sibling pages in this group cover those options. Forward-auth earns its place when the rules are too dynamic, too shared with other systems or too sensitive to copy into gateway configuration, for example when one policy engine must govern APIs, internal tools and data services together. Before you commit, confirm with your team which service will own the decision, who on-call will be when it fails, and whether the extra network hop on every request fits your latency budget. Measure that hop in your own environment, because the cost depends on where the authorization service runs relative to the gateway.
Forward-auth configuration options and when each fits
This table compares the main forward-auth settings a team chooses, from request method to failure behavior, and shows when each one suits the decision your auth service makes. Read each row as a question to answer before rollout, then confirm exact field names in the forward-auth documentation. The main takeaway is that the default timeout and failure code should be a deliberate choice, not an accepted default.
| Setting | Default or option | When it fits |
|---|---|---|
| Request method | GET by default, POST forwards the request body | Use GET when the decision depends only on headers, path and identity. Use POST when the auth service must read the request body to decide, and confirm body size limits first. |
| Authorization service URI | Set per route to your own auth endpoint | Point it at a dedicated, internally reachable service so the decision path can be scaled and monitored separately from the upstream. |
| Forwarded request headers | Client headers plus X-Forwarded headers added by the plugin | Forward only what the auth service needs to decide. Trim anything sensitive that the decision does not require, and confirm which headers the plugin adds by default. |
| Response headers passed on | Chosen auth response headers sent to the upstream or client | Use this to pass identity claims such as a user ID or role to the upstream. Pass only headers the upstream trusts, and strip any client-supplied copies of them. |
| Timeout | 3,000 ms by default | Keep the default for fast in-cluster auth services. Lengthen it only after measuring the auth service's tail latency, since every request waits for this call. |
| Behavior on network errors | Returns 403 by default | Keep fail-closed (403) for sensitive APIs. Consider a different failure response only after a documented risk review, because a permissive setting admits traffic while the auth service is unreachable. |
| Caching or degradation options | Confirm availability in the current docs | Ask whether the version you run supports any caching or degraded behavior, and test it before relying on it for production authorization. |
Which headers the auth service receives and what reaches the upstream
The forward-auth plugin copies the incoming request to your authorization service, and the service's response decides what the upstream sees. For each request, APISIX sends a subrequest to the external auth endpoint, using GET by default. The plugin adds X-Forwarded-* headers describing the original request, such as the client address, host, method and URI, so the auth service can make a decision on the original call rather than on the gateway's internal hop. Your auth service should read the headers it needs from the original request, return a 2xx response to allow the call, and return a non-2xx response to deny it. Confirm the exact header names and the set of request headers copied in the forward-auth documentation for your APISIX version, and test them against a staging auth service before you write any policy that depends on them.
Response headers only reach the upstream when you name them in the plugin configuration. The upstream_headers setting selects which headers from the auth response, typically a user ID, email or group claim, are forwarded to the backend, and client_headers selects which ones are returned to the caller. Any header you do not list is dropped. The more important control is the reverse direction: if a client sends its own X-User-Id or similar header, that value must never reach your application unless APISIX overwrites it. Plan for the gateway to remove or replace every identity header that clients can send before forward-auth sets its own copy, and confirm this behavior for your routes, including any route that skips the plugin.
Treat identity-header trust as a control you prove, not a default. Before rollout, send requests from an external caller that include forged identity headers, test with and without the forward-auth plugin on each route, and check what the upstream logs or echoes back. Repeat the test after any change to route order, plugin priority or upstream header settings, and record the results as part of your change evidence. If your backend cannot be certain that identity headers came from APISIX, restrict it to accept traffic only from the gateway, using network policy or mTLS, so forged requests never reach it.
How to set up forward-auth on an APISIX route
This sequence takes a forward-auth check from an authorization service you already run to a tested APISIX route that allows, denies and fails closed as intended. The result is a route whose decisions and identity headers you have verified in a non-production instance before any client traffic depends on it.
Confirm the authorization service and its endpoint
The platform or security team that owns the authorization service confirms the URL APISIX will call, the HTTP method it expects (GET by default, or POST if the request body must be evaluated), and the exact status codes it returns for allow and deny. Send one manual request to that endpoint with the headers APISIX will forward and record the response. This step is done when the service returns the expected allow and deny codes for known test credentials and the team has written down which response headers it will send back for identity.
Create a test route in a non-production instance
The gateway engineer creates a route in a non-production APISIX instance, pointing at a simple test upstream that echoes the headers it receives. Keep the route isolated from production hostnames and consumers so that test traffic cannot reach real backends. Confirm the route answers requests through the data plane port before adding any plugin, so that a later failure can be attributed to forward-auth and not to basic routing.
Enable forward-auth with the chosen method and headers
Add the forward-auth plugin to the test route, set the authorization service URI, and choose the request method. List in the configuration which client request headers, such as Authorization or cookies, the service needs, and which response headers from the service should be copied to the upstream or client. Keep the list short, because every header you pass is a header the upstream will trust. The step is complete when the route configuration is saved and the Admin API returns it without errors.
Set timeout and failure behavior
Decide how the route should behave when the authorization service is slow or unreachable. The plugin's default timeout is 3,000 ms, and its default on network errors is to return 403, which fails closed. Confirm both values against your own service-level expectations and write them into the route configuration explicitly. Then stop the authorization service in the test environment and confirm that requests return the code you chose, not an allowed response.
Verify denied requests return the service's code
Send requests with credentials the authorization service rejects, and confirm that the client receives the same status code the service returned, and that the upstream logs show no request arrived. Repeat with a missing credential. A denied request that reaches the upstream, or that returns a different code from the one the service sent, means the route configuration or the plugin's response handling must be corrected before you continue.
Verify allowed requests and identity headers upstream
Send a request with valid credentials and confirm that it reaches the test upstream exactly once, with no duplicate requests in the upstream logs. Inspect the headers the echo upstream received and check that the identity headers you configured are present, carry the expected values for the test identity, and that no header from the client claiming a different identity survives. This step closes when the upstream log shows one request per client call with the correct identity values.
Record the results before promoting the configuration
Save the route configuration, the authorization service responses used in testing, and the results of each allow, deny and failure case in your change record. Ask the team that operates the gateway to confirm the latency added per request by measuring the same route with forward-auth enabled and disabled. Promote the configuration to production only after the change record shows every case passed and the upstream trusts only the headers you intended to pass.
How latency and failure settings affect availability
With forward-auth, every request that passes through the gateway waits for an extra network call to your authorization service before the upstream sees it. That cost is paid on the hot path of all protected traffic, not only on logins or token issuance, so the added time compounds with every hop. The location of the auth service therefore matters as much as its code. Placing it in the same network segment or availability zone as the gateway keeps the round trip short, while a service reached across regions or through a slow shared load balancer can dominate end-to-end response time.
- Round trip per request: Each protected request makes one authorization call, so the added latency is the gateway-to-auth round trip plus the auth service's processing time.
- Placement: Co-locate the auth service with the gateway where your network design allows it, and measure the call from the gateway's own network position.
- Connection reuse: Confirm how the gateway reuses connections to the auth service, since repeated TLS handshakes add time on each call.
- Tail latency: Measure the 95th and 99th percentile of the auth call, not only the average, because slow outliers set the user experience under load.
- Load testing: Test with production-like request rates, since an auth service that is fast at low volume can queue at peak.
Before go-live, ask your team to measure the added latency per route under realistic peak load and to record the auth service's own capacity limits.
The forward-auth plugin times out a call to the authorization service after 3,000 ms by default. That value is a ceiling, not a target: a request that waits the full window before failing adds that delay to the user's experience and holds gateway resources while it waits. Setting the timeout too high lets a degraded auth service slow every protected route, while setting it too low turns normal latency spikes into authorization failures. The right value depends on your measured latency distribution and on how much delay your applications can tolerate.
- Default value: The documented default is 3,000 ms, which you should treat as a starting point to validate against your own measurements.
- Headroom: Set the timeout above normal tail latency so routine spikes do not become denials, but well below the point where users abandon requests.
- Per-route differences: Public APIs, internal tools and payment or data-change routes may need different values, so review them as separate policies.
- Upstream budget: Account for the time already spent in other plugins and in the upstream call, since the total must stay within your client timeouts.
- Retry behavior: Confirm whether the gateway retries the auth call, because retries multiply the wait when the service is slow.
Confirm the timeout unit and the exact field name in the forward-auth documentation for your APISIX version before you write it into configuration.
When the authorization service cannot be reached or does not respond in time, the forward-auth plugin returns 403 on network errors by default. That is a fail-closed outcome for the affected requests: the gateway denies access rather than passing traffic through unchecked. For user traffic this means an outage in the auth service becomes a visible outage for every protected application, even when the applications themselves are healthy. Fail-closed is usually the right security posture for sensitive routes, but the business impact must be understood and accepted per route.
- Default denial: Network errors and timeouts result in a 403 response unless you configure otherwise, so users see access failures rather than errors from the upstream.
- Distinguish errors: Ask whether your monitoring separates an auth service outage from a legitimate deny decision, because both can look like 403 to users.
- Route classification: Decide which routes must fail closed, such as administrative or financial paths, and which may tolerate a different outcome.
- Client messaging: Plan a clear error body or status for clients and support teams so authorization outages are not mistaken for application bugs.
- Dependency coupling: Recognize that the auth service becomes a shared dependency for every route that uses it, so its availability sets the availability of those routes.
Verify the failure status codes and their configuration keys in the forward-auth documentation, and test a simulated auth outage in a non-production environment before relying on the behavior.
Degradation settings let you choose how the gateway behaves when the authorization decision cannot be obtained, and caching can reduce how often the auth service is called at all. Both options trade availability against security. A cached allow decision can keep users working during a short auth outage, but it also keeps revoked access alive for as long as the cache holds it. Any fallback that allows traffic without a fresh decision needs explicit sign-off from security and compliance owners, and the settings must be confirmed against the current vendor documentation rather than assumed from older examples.
- Fallback options: Identify which degradation settings the forward-auth plugin supports in your APISIX version, and what each one allows through.
- Cache scope: If caching is available, confirm what the cache key contains, how long entries live and whether revocations invalidate them.
- Revocation delay: Quantify with your identity team how long a revoked user could still pass under any cached or fallback decision.
- Sensitive routes: Exclude high-risk routes from any degradation path, and document that exclusion in the route design.
- Evidence: Keep records of the chosen settings, the approval, and the test results, since auditors will ask how the gateway behaves during an authorization outage.
Ask the vendor documentation and your security owners to confirm each degradation and caching setting before you enable it on any production route.
Security and operational risks of delegated authorization
Forward-auth puts an external service in the path of every request the gateway handles, so the risks cluster around three questions: who can be believed about identity, who can reach the decision endpoint, and how the decisions are recorded and kept fast. The cards below cover each area as it applies to APISIX forward-auth specifically.
Header spoofing at the trust boundary
Forward-auth copies selected headers from the authorization service's response to the upstream or client, so the upstream trusts whatever arrives there. Strip any identity headers a client can send before the auth step runs, and confirm that only the headers you explicitly list are forwarded. Test by sending a request with a forged identity header through the gateway and checking that the upstream sees only the value your auth service returned.
Locking down the auth endpoint
The authorization service should accept calls only from APISIX nodes. Place it on a private network segment or behind a policy that allows the gateway's addresses alone, and use TLS on the connection if it crosses a network boundary you do not fully control. An exposed decision endpoint lets any caller probe your policy or send forged subrequests that the service may accept as coming from the gateway.
Audit logging of allow and deny decisions
Record each allow and deny decision with the route, the consumer or subject identifier, the time and the reason returned by the auth service. Keep the gateway access log and the auth service log correlated through a shared request ID, so an investigator can tie a denied call to the policy version that produced it. Ask your auth team which fields they log and where the logs are retained, because the gateway log alone rarely records why a request was refused.
Capacity and monitoring of the auth service
The auth service sits on every protected request, so its latency and error rate become the gateway's latency and error rate. Monitor response time percentiles, error counts and timeouts from the auth service side, and set capacity against your peak request rate with headroom. Plan the behavior when the service is slow or unavailable: the default timeout and the 403 response on network errors decide whether a brief outage blocks all protected traffic.
Forward-auth questions buyers ask before hiring help
How much effort does a forward-auth setup take and what drives it?
Effort depends mostly on the number of routes that need the check, how mature your existing auth service is, how precisely the header contract is defined, and how much testing the rollout needs. A single route protected by an auth service that already returns clear allow and deny responses is a small change. Ten routes with different policies, an auth service that has not been load tested, and no written list of the headers passed upstream are a much larger job. Ask any partner to break the quote into design of the header contract, gateway configuration, failure-mode testing and rollout, so you can see which driver dominates your case.
Can our team set up APISIX forward-auth on its own?
Yes, if you have engineers who can run APISIX in your infrastructure, write route and plugin configuration, and test denials and timeouts against a real auth service. Many teams do the first routes themselves and bring in help for the parts that are easy to get wrong: the header trust model, what happens when the auth service is slow or down, and how the check is tested before traffic moves. Be honest about who will own the configuration after go-live, because forward-auth decisions sit on the request path of every protected route.
What does the work need from our side?
The work needs named owners for the auth service and its API contract, network access between the gateway and the auth service, and TLS settings that both sides accept. You also need test identities for each role or consumer type, including accounts that should be denied, so that denials are verified and not assumed. Plan for someone who can confirm which response headers the upstream services already trust, because forward-auth can pass identity headers to them. Ask the partner to list these inputs in the written scope so gaps show up before the build starts.
Should we use forward-auth, openid-connect or jwt-auth?
The choice depends on where the identity decision should live. Use jwt-auth when your own services issue JWTs that APISIX can verify locally. Use the openid-connect plugin when an identity provider should handle login or token validation for browser or API clients. Use forward-auth when your existing auth service already holds the authorization logic and you want the gateway to ask it on each request. Forward-auth adds a network call to every request, so compare its latency and failure behavior with the alternatives. The APISIX forward-auth plugin documentation describes the request and response behavior you will need to design around.
How long does a forward-auth pilot take?
A pilot's length depends on how many routes it covers, whether the auth service already has the response contract the gateway needs, how many failure cases you test, and how quickly your change process approves configuration. A pilot on one non-critical route with an existing auth service is much shorter than a pilot covering several production routes with timeout and degradation tests. No duration can be stated before the scope is written. Ask for a schedule with milestones tied to the routes, test cases and approvals in that scope, not a generic estimate.
What does a fixed-scope quote cover?
A fixed-scope quote follows from a written scope that lists the routes in scope, the auth service and its contract, the header rules for upstreams, timeout and failure-code settings, the test cases, the deliverables and the timeline. It should state what the client provides, such as access and test identities, and what is excluded, such as changes to the auth service itself. DeployOpen does not publish a price list, so the quote is produced from that scope. Ask whether the quote covers one-time build work only, and whether ongoing support or upgrades are a separate retainer.
Running forward-auth after go-live
Once forward-auth is live, the auth service sits on the path of every request it protects, so its health is the gateway's health. Monitor the authorization service's response times and error rates separately from gateway latency, and set alerts for both. Track how often the plugin returns 403 because the auth service was unreachable or timed out, since the default behavior fails closed and a silent auth outage looks like a wave of denied users. Compare those counts with your auth service's own logs to confirm that the failures start where you expect. Confirm with your gateway team which access logs and metrics your APISIX build exposes for the plugin, because the exact metric names depend on the version and observability plugins you enable.
Changing authorization rules should not require editing every route. Keep the decision logic in your auth service and treat the forward-auth settings on the route (the auth URI, the headers you pass through, and the timeout) as stable configuration that changes rarely. Confirm the operational path for your deployment mode before you plan any change. In traditional mode, changes go through the Admin API to the etcd cluster. In decoupled mode, the control plane publishes changes that the data plane picks up. In standalone mode, the configuration lives in conf/apisix.yaml or conf/apisix.json, which APISIX checks for changes every second and hot-reloads. Ask your team who is allowed to change the file or the Admin API, and whether those changes go through version control and review.
Plan rollback before the first change. Keep the previous plugin configuration in version control or a backup you can apply quickly, and rehearse restoring it in a non-production environment. Under standalone mode, rollback usually means restoring the previous file version and letting the reload take effect, which you should time and verify. Under traditional and decoupled modes, confirm how the Admin API or your automation reverts a route, and whether the revert reaches every gateway node. After any rollback, run a set of allowed and denied test requests against the protected routes and check the upstream logs to confirm that the identity headers match what the auth service returned.
How DeployOpen works on this
DeployOpen plans, implements, tests and documents Apache APISIX external authorization with forward-auth inside your own infrastructure. We start from the decision your auth service must make on each request, then build and verify the gateway configuration around it, so your team can operate it after handover.
How we scope
- We agree in writing which routes use forward-auth and which stay on other authentication plugins.
- The written scope names your auth service, the headers it returns, and the upstreams that receive identity headers.
- We record the timeout, failure code and degradation behavior you choose for each route before build starts.
- Engagements are fixed-scope, so deliverables and timelines are agreed in writing before work begins.
How we build
- We configure the forward-auth plugin with the request method, forwarded headers and response headers you define.
- We test allow, deny and unreachable-service paths against your auth service in a non-production environment.
- We measure added latency per request on representative routes and report results to your team.
- We validate identity header stripping and pass-through so upstreams cannot receive spoofed values from clients.
How we hand over
- You receive route and plugin configuration as files, with a record of each decision and its reason.
- Runbooks cover timeout tuning, failure codes, degradation settings and rollback steps for your operators.
- We walk your platform, security and application teams through the test results and open risks.
- Ask us to confirm which configuration lives in your repository and which parts your team owns after handover.
How we support
- Ongoing support, upgrades and continuous testing are available as retainers after the initial delivery.
- A retainer can cover APISIX version upgrades, plugin behavior changes and periodic re-testing of authorization paths.
- Support scope and response arrangements are set out in the written agreement, so ask for them during quoting.

