Skip to main content

HTTP caching for MENA SaaS: keep private responses private

A practical guide to browser and CDN cache boundaries, current authorization during ETag revalidation, and tenant-safe Arabic-English responses.

Updated 8 min readMarkdown
Part of seriesBuilder Guides
HTTP caching for MENA SaaS: keep private responses private

An operator opens an invoice in a Saudi customer workspace. Another operator requests the same endpoint from a UAE workspace. Both are signed in, but they must not receive the same confidential response merely because an intermediary considers its copy fresh.

These are hypothetical product workspaces, not country-specific legal requirements. This guide proposes a conservative architecture for authenticated HTML and JSON: keep confidential responses out of shared caches, then decide whether browser storage is acceptable. It does not prescribe a framework or claim measured performance.

Cover: original SultanByte artwork showing confidential responses, shared-cache bypass and a separate browser-storage decision.

Name the cache before choosing the header

A browser HTTP cache and a CDN cache have different audiences. RFC 9111 distinguishes a private cache for one user from a shared cache serving multiple users. A reverse proxy can also be a shared cache. The application's login screen does not define those boundaries.

Inventory every response path: origin, reverse proxy, CDN and browser. Record who owns each policy. Include server-rendered HTML containing customer data, API responses and error pages that disclose private details. Reviewing only /api/ leaves the document response outside the review.

Keep a separate inventory for application-managed copies. MDN's Cache API documentation explicitly says that API does not honor HTTP caching headers. A service worker that saves responses therefore needs its own policy. This guide's HTTP controls are not a browser-cleanup system; SultanByte's shared-device logout guide covers that handover problem.

Choose between storage and revalidation

The unqualified directives below have distinct meanings in RFC 9111's response directives:

  • private prohibits shared-cache storage but permits private-cache storage under the applicable rules. It does not require a browser to check permission again before every reuse.
  • no-cache permits storage but requires successful validation before reuse. The name does not mean “never save this response.”
  • no-store prohibits storage of the immediate request and response by compliant HTTP caches. It is not a complete privacy guarantee.

Start confidential responses in this portal with:

Cache-Control: no-store

Make an explicit exception only where the product accepts browser storage. A read-only workspace view might instead use:

Cache-Control: private, no-cache
ETag: "opaque-representation-version"

That example is a policy choice, not an instruction to make every authenticated response cacheable. It allows a browser-held representation while requiring validation for ordinary HTTP reuse. MDN documents that history navigation is a separate consideration: restoring an earlier page is not necessarily a new validating request.

Changing a header also does not recall old copies. MDN's cache-clearing explanation notes that response directives cannot clear already-stored intermediate responses. A previously fresh copy might prevent the new header from being fetched at all. Treat a switch to no-store as a migration: correct the policy, invalidate affected shared entries, and assess browser copies separately.

Authorize before returning 304

An ETag describes a representation; it is not a permission token. RFC 9110 defines conditional requests using If-None-Match, including a 304 Not Modified response for a matching conditional GET. Its precondition ordering puts normal request checks before conditional evaluation. OWASP calls for permission checks on every request.

The origin flow is therefore: authenticate the current request, resolve its workspace, authorize the resource and permitted fields, then select the representation and evaluate its validator. Do not put a generic ETag shortcut ahead of authorization middleware.

Suppose a finance operator stores an invoice response and later loses invoice access. Their browser sends the old ETag. The next request must receive the application's appropriate denial, not 304, even if the invoice has not changed. Under this design, apply no-store to denial responses too, and do not use an old successful response as the fallback when authorization is unavailable.

For allowed requests, compare the validator for the representation that this requester may receive now. A role change that removes fields must not validate the earlier, fuller response. Include the relevant representation dimensions in an opaque validator scheme; avoid exposing account details in the ETag itself. This is a proposed implementation rule derived from the distinction between resource identity and selected representation, not a universal ETag format.

Proposed HTTP cache decision flow: classify confidential responses, bypass shared storage, optionally revalidate a browser copy after current authorization, and test tenant and language boundaries

Original infographic: SultanByte. Proposed framework informed by IETF, AWS, Cloudflare, OWASP and MDN documentation, November 2026. These are design recommendations, not benchmark results.

Check the deployed CDN policy

An origin response is only part of the configuration.

AWS's CloudFront cache-policy documentation warns that a minimum TTL above zero causes caching for at least that period even when the origin sends no-cache, no-store or private. Setting the minimum to zero removes that particular override; it does not itself disable caching.

For this proposed confidential route, use an explicitly non-caching behavior. AWS's managed CachingDisabled policy sets minimum, default and maximum TTLs to zero. Confirm that the actual request path selects that behavior. Separately verify that the origin receives the credentials and workspace context needed for authorization; disabling caching is not an authentication configuration.

On Cloudflare, Cache Rules settings provide a bypass option. They also provide an Edge TTL option that ignores origin cache-control, while status-code TTL rules can override it too. The proposed portal policy is an explicit bypass for confidential routes, with public assets handled separately.

Review the entire matching rule set. Cloudflare's ordering documentation says matching rules can combine, and the last matching rule wins when settings conflict. An earlier bypass can therefore lose to a later eligibility rule. Check browser TTL settings as well as edge settings, and inspect any Worker or reverse proxy on the delivery path.

Do not infer safety from one cache-status label. Cloudflare documents that a bypass configuration may show DYNAMIC. Require the expected response body and authorization behavior, supported by origin request records, rather than a particular reassuring header alone.

Separate tenants, languages and permissions

A tenant ID in a URL is a selector that the server must authorize, not proof of membership. Keep confidential responses outside shared caching even when the URL names the workspace. Two people in the same workspace may have different field permissions.

For browser-revalidated responses, record what changes the bytes: workspace, resource version, permitted field set and resolved language. Consider a hypothetical Arabic invoice summary and its English equivalent. If the server renders translated labels, those are distinct representations. If the API returns identical language-neutral data and the client translates labels, do not invent a server-side language distinction.

When a request header selects a representation, RFC 9111's Vary rules govern cache matching. For example, Vary: Accept-Language is relevant when that header actually determines the response. It cannot describe a language selected only from a database preference.

For any deliberately shared public endpoint, verify provider behavior rather than assuming the header configures everything. Cloudflare's Vary documentation describes configurable header actions and normalization. Confirm that those choices agree with origin selection. Keep this review focused on response identity; it does not require changing the portal's bilingual routing framework.

Test the boundaries before release

Run these cases with synthetic records through the deployed non-production delivery path. Keep normal browser caching enabled for relevant cases. These are proposed acceptance criteria, not reported test results.

  • Cold and repeated confidential read: Fetch a no-store response twice, using a harmless origin request marker. Require current authorization on each network request and no shared-cache replay. Inspect the delivered policy, not just the origin configuration.
  • Warm-cache tenant crossover: Prime an endpoint as workspace A, then request it as workspace B and without credentials. Require B's permitted representation or denial, never A's synthetic marker. Repeat through each supported hostname and proxy route.
  • Revocation with an old ETag: Store a browser-revalidated response, remove membership, then send its If-None-Match. Require denial rather than 304. Repeat with a role downgrade and require a newly permitted representation when its fields differ.
  • Arabic-English representation change: Request each server-rendered language with the other language's validator. Require the selected language and correct validator behavior. Repeat after changing a stored language preference; do not accept a stale translation simply because the record version is unchanged.
  • Configuration regression: In an isolated fixture, exercise a conflicting CDN rule or positive CloudFront minimum TTL. Require the release check to detect shared reuse or a policy mismatch. Restore the intended configuration and repeat before approving deployment.
  • Policy migration and origin failure: Seed an older cacheable synthetic response, deploy no-store, and exercise the documented invalidation procedure. Separately interrupt validation for a private, no-cache response. Require that ordinary HTTP reuse does not silently substitute the old confidential body when validation fails.

Make exceptions reviewable

The release record should name each confidential route's browser policy, effective shared-cache policy and authorization owner. Attach evidence from the acceptance cases. Recheck it when a CDN rule, proxy or response format changes.

Keep file-delivery exceptions separate: SultanByte's private-download guide covers signed grants and authenticated delivery. For ordinary portal responses, begin with shared-cache bypass and no-store. Permit browser revalidation only after the team accepts local storage and demonstrates that a matching ETag cannot bypass current access checks.

About the author

  • I have lead the Engineering for multiple startups in UAE. I also have my own agency qualascend.com.

No comments yet. Be the first to comment.