As discussed in the post introducing this series, if users open an application that requires authentication for the first time in a while, they might see an error when the application first loads. Somewhat confusingly, the error goes away after a browser refresh. Let's dig into what's going on here.
Redirects after reload
Debugging using browser dev tools, you might notice the following interesting behaviour: when the app is first opened, the HTML and JS files are loaded fine. After a refresh, the request to the application root receives a redirect response triggering the authentication flow. At first, that might seem odd: shouldn't the browser have sent a request that triggered an authentication redirect when the page was first loaded? It turns out that it doesn't because it decides that the response it received previously is probably good enough, as described in the next section.
Heuristic caching
You may know that responses to certain HTTP requests can be (and are) cached. The caching can be done by the browser itself, by the server, or any intermediaries including proxy servers, content delivery networks and application gateways. To understand the observed behaviour, you need to know one more thing: if there are no specific caching directives (in the HTTP response), resources will be cached on a best effort basis known as heuristic caching.
The mdn web docs provide a great overview of heuristic caching; the gist is that, without explicit caching instructions, it's reasonable to assume that if a resource hasn't changed for a long time it probably won't change for a short while longer. For example, if the HTML file served when browsing to the app root hasn't changed for a month, you can probably keep using it for a few more days without problem.
That explains the observed behaviour: when a user opens the application in their web browser, the browser considers the cached HTML and JS files that it had to be fresh enough not to actually send an HTTP request to get the resources. As a result, there is no authentication redirect when the app is loaded for the first time.
So what's different about a browser refresh?
The reload function in your browser
Reload HTTP requests include headers that force going all the way to the web server (bypassing cached resources) to check whether there is a new version of the resource available. That's why users see the error disappear after a browser refresh (usually they're still logged in with their identity provider, so all the browser redirection happens somewhat invisibly from their perspective).
Cache-Control
So, errors on first load are caused by the browser using its heuristics to decide to serve the app's HTML from its cache, even when the authentication token for the web server has expired. The front end also loads because the JS bundle is available in the cache. When the request for the HTML reaches the server (for example due to browser refresh), regular authentication redirect can take place, and all is well again. The fix then, it seems, is to direct the browser to always have the request for the HTML go all the way to the server if its authentication details have expired. More complex options are available (and better in some circumstances such as high traffic), but the simplest way to achieve this is to ensure that the request always goes to the server, irrespective of the freshness of the cached page.
There are a few different options for how to do this, but the most appropriate is to include no-cache
in the values for the Cache-Control
header. This will force the browser to contact the web server to validate that its cached version is up to date, using something like an ETag.
To reiterate: the main purpose of using no-cache
here is for the side effect of being redirected to the authentication flow and receiving a fresh authentication cookie; retrieving the latest version of the resource is a nice bonus but a secondary concern.
Thoughts
Some smart people have designed some clever mechanisms to make the web work well. One of those is effective caching, to reduce load on servers and the network, even when not explicitly told to do so. If you want to be sure what the caching behaviour will be for resources served by your web server, you need to set the appropriate HTTP response headers. You should probably be doing this anyway; as the mdn web docs say, "basically all responses should explicitly specify a Cache-Control
header."
I hope that that was an interesting and enlightening article, and that it helped you if you are seeing similar errors. Come back soon for the next installment, which will discuss how to handle errors caused by inactivity while an application is still open.