Skip to content

feat(forward-auth): cache authorization decisions at the edge#13664

Open
shreemaan-abhishek wants to merge 2 commits into
apache:masterfrom
shreemaan-abhishek:perf/forward-auth-cache
Open

feat(forward-auth): cache authorization decisions at the edge#13664
shreemaan-abhishek wants to merge 2 commits into
apache:masterfrom
shreemaan-abhishek:perf/forward-auth-cache

Conversation

@shreemaan-abhishek

Copy link
Copy Markdown
Contributor

Description

forward-auth makes a synchronous HTTP call to the external authorization service on every request, which dominates its per-request overhead. This adds an opt-in per-worker cache of authorization decisions so repeated calls from the same caller can be served without hitting the auth service.

A new optional cache block:

"forward-auth": {
    "uri": "http://auth.internal/verify",
    "request_headers": ["Authorization"],
    "cache": {
        "key_headers": ["Authorization"],
        "ttl": 30,
        "include_method": false,
        "include_uri": false
    }
}

When present, an allow/deny decision is stored in a per-worker resty.lrucache and replayed on subsequent matching requests.

Because this is an authorization path, correctness is prioritized over speed:

  • Never cache across identities. The cache key is namespaced per plugin conf id + version (via plugin_ctx_id, so config edits invalidate it) and built from the operator-declared key_headers values, optionally the method/URI, and — for POST — an md5 of the forwarded body. key_headers is required whenever caching is enabled, so there is no implicit "cache everything" default.
  • Respects upstream cache-control. no-store / no-cache / private skip caching; max-age caps the TTL (and max-age=0 skips).
  • Fails closed on transient errors. Auth-service 5xx responses are never cached.
  • Faithful replay. Both allow (upstream headers) and deny (status, body, client headers) outcomes are reproduced on a hit, identical to the uncached path.
  • Fully backward compatible: with no cache block the plugin behaves exactly as before.

Which issue(s) this PR fixes

N/A (enhancement).

Checklist

  • I have explained the need for this PR and the problem it solves
  • I have explained the changes or the new features added to this PR
  • I have added tests corresponding to this change
  • I have updated the documentation to reflect this change
  • I have verified that this change is backward compatible (if not, please discuss on the APISIX mailing list first)

Tests

New t/plugin/forward-auth4.t counts actual auth-service invocations via a shared dict and asserts:

  • schema requires key_headers and enforces ttl bounds
  • identical requests hit the cache (1 auth call for N requests)
  • a different identity is never served another's cached decision, and a cached deny stays a deny
  • caching disabled still calls the auth service every time
  • upstream Cache-Control: no-store is honored (not cached)
  • include_uri keys on the request URI
  • the POST body is part of the cache key

Existing forward-auth.t / forward-auth2.t / forward-auth3.t still pass unchanged.

forward-auth makes a synchronous HTTP call to the authorization service
on every request. Add an opt-in cache block that stores allow/deny
decisions in a per-worker lrucache, cutting calls to the auth service
for repeated callers.

Correctness guardrails, since this is an auth path:
- Cache key is namespaced per plugin conf/version and built from the
  configured identity headers, optionally method/URI, and (for POST) a
  hash of the forwarded body, so decisions never cross identities.
- Honors upstream Cache-Control (no-store/no-cache/private skip caching,
  max-age caps the TTL) and never caches transient 5xx; short opt-in TTL
  by default.
- Both allow and deny outcomes are replayed faithfully on a hit.

Adds t/plugin/forward-auth4.t (hit/miss, identity isolation, deny
replay, no-store, include_uri, POST-body keying) and documents the
cache attributes with an example.
@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. enhancement New feature or request labels Jul 6, 2026
- fold scheme, host and client IP into the cache key (all are forwarded
  to the auth service, so a decision is never reused across a different
  origin or client address)
- hash the POST body with SHA-256 instead of MD5 (collision-resistant)
- honor Cache-Control s-maxage ahead of max-age for the shared cache
- fail fast if the LRU cache cannot be created
- cover context (Host) keying with a new test case
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant