Search code examples
javascriptcachingcsrfservice-worker

How to reduce service worker cache size when caching requests with CSRF tokens


I have a service worker that is caching requests from the browser, so the page works offline. However, each time the user logs out and back in, a new CSRF token is generated and all previous cached data is useless since the requests contain the CSRF token as part of the querystring. This necessitates re-caching all the same data again, so we're left with multiple copies of the data in the cache, each copy simply having a different request URL due to the different CSRF tokens.

I'm querying the network-first, then failing over to cache if the network is unavailable.

How should I handle the caching of these responses in relation to the CSRF token? Should I manually remove the CSRF token from the event.request value before doing the cache.put() and cache.match() functions? Is this even permitted? By modifying the request URL, it seems the previously cached value for that request could still be returned, even if the user has logged out and back in, which would be the desired behavior.

Also, how can I remove all cached requests that don't match the current CSRF token, without purging all entries from the cache?

Here's the pertinent Service Worker code:

self.addEventListener('fetch', function(event) 
// 'fetch' event lister: if the network is UP, fetch the data across the network and cache the result.
// If network is unavailable, attempt to fetch from cache.
{
    // Send a response, first by trying the network, then by looking in cache.  If both fail, an error occurs.
    event.respondWith(
        // Try to fetch the request from the network:       
        fetch(event.request)
            // If successful, cache a clone of the response, then return it.
            .then(function(response)
            {
                var r = response.clone();

                caches.open('offline-cache')
                    .then(function(cache)
                    {
                        cache.put(event.request, r);
                    })
                    .catch(function(error)
                    {
                        console.log("Unable to cache item: ", error);
                    });

                return response;
            })
            // If network fails, try to pull the item from cache.
            .catch(function(error)
            {
                // Open the cache
                return caches.open('offline-cache')
                    // When cache is open, attempt to match with desired request
                    .then(function(cache)
                    {
                        // Try to match:
                        return cache.match(event.request)
                        // If successful, return the match.  Errors bubble up to the main event.
                            .then(function(response)
                            {
                                return response;
                            });
                    })
                    .catch(function(error)
                    {
                        console.log("Cached entry not found. Error.");
                    });
            })
    ); // END event.respondWith
});

Solution

  • If resources to be cached are identical for all requests/users and can be identified by removing certain URL parameters (or removing the query string entirely), your code can generate a cache key URL and use that String instead of Request objects for the Fetch and Cache APIs' methods:

    // Returns URL string used for caching that excludes authentication-specific URL params, etc
    function getCacheKeyUrl(request) {
      // Strip query string from URL
      return request.url.replace(/\?.*/,'');
    }
    
    self.addEventListener('fetch', event => {
      const cacheKeyUrl = getCacheKeyUrl(event.request);
    
      ...
    });
    

    Then you can use fetch(cacheKeyUrl), cache.put(cacheKeyUrl, r), cache.match(cacheKeyUrl), etc in your code.