Troubleshooting Service Worker Authentication Issues
Service Worker authentication can seem unreliable at times, but understanding its mechanics can help you build a stable authentication process for your web app that works every time.
Note: You can review the full code for the Bytescale Service Worker and the Bytescale JavaScript SDK on GitHub to gain a better understanding of how the two communicate.
How does Service Worker Authentication Work?
Service Worker authentication involves running a small bit of JavaScript in the browser's background. This script, the Bytescale Service Worker, attaches Authorization
headers containing your JWT to all outgoing requests to the Bytescale CDN.
Understanding the authentication workflow is essential for identifying why issues occur.
The authentication workflow is as follows:
- Browser loads page.
- Bytescale Service Worker may or may not be running from a previous session. If it's running and holds a valid JWT, it will immediately authenticate requests (such as
<img>
elements)*. - Browser loads resources (
<img>
,<script>
, etc.). - Your code calls the
beginAuthSession
on the Bytescale JavaScript SDK. - Bytescale JavaScript SDK calls your API's JWT endpoint.
- Bytescale JavaScript SDK receives a JWT from your API.
- Bytescale JavaScript SDK starts a new Bytescale Service Worker (if one is not running)*.
- Bytescale JavaScript SDK sends the JWT to the Bytescale Service Worker.
- Bytescale Service Worker stores this JWT in a variable scoped to the lifetime of the worker.
- Bytescale Service Worker is ready to authenticate requests.
- Bytescale JavaScript SDK periodically obtains a new JWT from your API (repeat from step 5).
- Last open tab for your web app is closed or is hard-refreshed:
- Browser may terminate the Bytescale Service Worker.
- Step 11 will cease, so even if the Bytescale Service Worker survives, it may contain a stale JWT the next time your web app is opened, resulting in failed requests.
* Each web app domain has a single Bytescale Service Worker, regardless of the number of open tabs or windows. The browser may terminate the Service Worker to save memory when all tabs and windows are closed. The Bytescale JavaScript SDK also restarts the Service Worker on every hard refresh to address a known issue where existing Service Workers don't receive "fetch" events on hard-refreshed pages, necessitating a new Service Worker installation.
Why does Authentication Fail?
There are several potential race conditions in the above workflow that result in page elements loading before the Service Worker is ready to authenticate them. When this occurs, those requests will fail.
These scenarios are as follows:
Initial Page Load:
Consider the large time delay between step 3 (loading elements) and step 10 (auth ready): this will cause all media elements that are initially present on the page to fail.
Hard Refresh:
The Service Worker is unregistered at the start of a hard refresh, resulting in a similar flow to an initial page load (above).
All Tabs Closed:
If all tabs are closed (step 12 above), the Service Worker might be terminated to save memory. If this occurs, or if the Service Worker is hibernated and reactivated with a stale JWT, then a new JWT must be requested from your API. As with the above scenarios, this will result in the page's initial set of elements failing to load.
Solution
There is a simple solution to solve all authentication race conditions:
- Ensure you only attempt to load private files after the
AuthManager.beginAuthSession()
promise succeeds, rather than immediately on page load. - The
AuthManager.isAuthSessionReady()
method returnstrue
only after thebeginAuthSession()
promise succeeds. Therefore, you can use this method elsewhere in your code to verify if the browser is ready to download private files from the Bytescale CDN.
For example, you could call isAuthSessionReady()
before attempting to render <img>
elements into the DOM that reference private images.
This way, your resources will load the first time the browser requests them.
Example Implementation:
For media elements such as <img>
, <video>
, and <audio>
, create a higher-level abstraction within your web app to synchronize their loading with the authentication process.
Start an Authenticated Session:
Call
AuthManager.beginAuthSession()
in your web app.Create a Private Component:
In your web framework (e.g., React), create a new component, such as
PrivateImage
, with asrc
property.In the
PrivateImage
component:
- If
AuthManager.isAuthSessionReady()
istrue
, render an<img>
element. - If
false
, wait 100ms and check again.
(Optional) Optimization:
When reloading a page or opening new windows/tabs, if the Service Worker already has a valid JWT, your images can load immediately without waiting for
isAuthSessionReady()
.To optimize the
PrivateImage
implementation above, attempt to load the image immediately. If the image fails, detect the error with JavaScript and revert to the default behavior of waiting forisAuthSessionReady()
to complete before loading the image again.This ensures a seamless experience by leveraging the existing session for quicker access while providing a fallback in case of issues.
By following these steps, you can ensure that your media elements are only loaded when authentication is ready, thus preventing authentication issues.