Make WordPress Core

Opened 3 weeks ago

Last modified 5 days ago

#63636 accepted enhancement

Enable instant page navigations from browser history via bfcache when sending "nocache" headers

Reported by: westonruter's profile westonruter Owned by: westonruter's profile westonruter
Milestone: 6.9 Priority: normal
Severity: normal Version: 6.3
Component: Administration Keywords: has-patch dev-feedback needs-unit-tests
Focuses: performance, privacy Cc:

Description (last modified by westonruter)

In #21938 and #61942 the no-store directive was added to responses when nochache_headers() is called. This happens with every authenticated response, and it may often be called when serving unauthenticated responses in e-commerce plugins, such as on the cart and checkout pages. This no-store directive prevents proxies from caching the response so that it is not erroneously served to other users. This is a good, however no-store has a downside: it disables back/forward cache (bfcache). This means that authenticated users can get a degraded experience since may not experience instant back/forward navigations enabled by the browser's bfcache. Furthermore, the lack of bfcache can result in data loss when data has been entered via a JavaScript-built UI since this state is lost when a page is not restored via bfcache. (See demo video in WooCommerce for a no-store removal PR which is now merged.)

(There are other reasons for why bfcache may be disabled for a page. One example is the use of the unload event; this was removed in #55491 in order to enable bfcache, only to realize that no-store had been recently added in #21938 so no navigation benefit was gained.)

The use of the no-store directive to prevent proxies from caching responses is actually redundant in this way with the private directive:

The private response directive indicates that the response can be stored only in a private cache (e.g., local caches in browsers).

You should add the private directive for user-personalized content, especially for responses received after login and for sessions managed via cookies.

If you forget to add private to a response with personalized content, then that response can be stored in a shared cache and end up being reused for multiple users, which can cause personal information to leak.

The no-store directive also does this, but it prevents the browser from also caching the response in bfcache:

The no-store response directive indicates that any caches of any kind (private or shared) should not store this response.

Nevertheless, the disabling of the bfcache via no-store was actually intentional in #21938. With bfcache there is a privacy concern where an authenticated user may log out of WordPress, only for another person to access the computer and click the back button in order to view the contents of the authenticated page loaded from the bfcache. In practice this issue depends on the user being on a shared computer, and it also requires the malicious user to act soon since the bfcache has a timeout (10 minutes in Chrome for pages sent without no-store). As noted in a comment, it's not entirely clear if the increased privacy is worth the degraded user experience if the private directive is already preventing proxies from caching the responses.

Nevertheless, the privacy concern can still be addressed without disabling bfcache. Protecting against restoring pages from bfcache after the user has logged out can be achieved as follows: When authenticating to WordPress, a "bfcache session token" cookie is sent along with the other authentication cookies. This cookie is not HTTP-only so that it can be read in JavaScript; it is a random string not used for any other purpose. When an authenticated page is served, a script is included which reads the value of this cookie. When a user navigates away from the page and then navigates back to it, a pageshow event handler checks to see if it was restored from bfcache. If so, it checks the latest value of the cookie, and if it doesn't match it clears the contents of the page and initiates a page reload so that the contents are not available.

Related tickets:

Change History (6)

This ticket was mentioned in PR #9131 on WordPress/wordpress-develop by @westonruter.


3 weeks ago
#1

  • Keywords has-patch added

#2 @westonruter
3 weeks ago

  • Keywords dev-feedback needs-unit-tests added
  • Status changed from assigned to accepted

#3 @westonruter
3 weeks ago

I've also formulated the patch in a standalone plugin: https://github.com/westonruter/nocache-bfcache

Last edited 3 weeks ago by westonruter (previous) (diff)

#4 @westonruter
3 weeks ago

I just learned of the Clear-Site-Data header as well.

Upon logging out, the following header could get sent:

Clear-Site-Data: "cache"

According to MDN, it says this "might" clear out the bfcache "depending on the browser". In testing with Chrome, however, this doesn't seem to work. So the JavaScript-based client-side bfcache invalidation still seems needed.

This remains an open question in w3c/webappsec-clear-site-data. There's another executionContexts directive which seems to have been intended for this purpose, but it is not (any longer) implemented by browsers.

Last edited 3 weeks ago by westonruter (previous) (diff)

This ticket was mentioned in Slack in #core-performance by westonruter. View the logs.


2 weeks ago

#6 @westonruter
5 days ago

  • Description modified (diff)
Note: See TracTickets for help on using tickets.