Service workers to manage our JWT tokens

Managing authentication (AuthN) on the web can be challenging, especially in a micro-service architecture. Web security features such as Cross-Origin Resource Sharing (CORS) and cookie attributes aim to improve HTTP mechanism security. Still, these can lack the controls and granularity needed to implement custom solutions securely.

Many of our micro-services are accessed via separate subdomains but authenticate requests using the same token. For example, a user may be logged in on store.grabyo.com but needs some data from api.grabyo.com. Using the same JSON Web Token (JWT), which controls their session on store.grabyo.com, they can authenticate their request to api.grabyo.com.

Product constraints

The one seemingly simple solution would be to put our services behind a single “gateway” subdomain, but specific product requirements prevented us from doing this. So instead, this solution would typically involve setting up a proxy server that would handle all incoming requests and forward them to the relevant services.

Grabyo’s business is focused on live video, so low latency is essential, but we serve users worldwide, which makes managing latency a challenge. For example, if a request has to go through a Gateway service before reaching its destination, that adds latency, so we wanted to avoid that. “Edge” computing solutions aim to alleviate this issue, but this still introduces another hop and thus latency for the request.

On top of that, some of our video technologies require different technology stacks and subdomains, making using a single “gateway” impossible.

AWS CloudFront installations. Edge computing brings servers physically closer to users, reducing round-trip latency.

The optimal solution would send the request directly from the user’s device to the end service, avoiding as many proxies, gateways and DNS lookups as possible.

In-browser solutions

Given the AuthN and latency constraints, there are a couple of conventional ways we could achieve this in the browser:

Widely-scoped cookies

We could set the JWT in a cookie scoped to *.grabyo.com. This would result in all requests to any grabyo.com subdomain being sent off with a JWT in the cookie header to work. However, not all of these services implement functionality that requires that authentication token.

According to the Principle of Least Privilege, a service should only have access to information it needs, so we didn’t want services that did not require the JWT to be sent, which increases the risk of leakage.

“Authorization” HTTP Request Header

Attaching the JWT to an XHR request’s Authorization header would allow us in the application code to choose whether or not to attach the JWT to any given request.

This approach also faces limitations because, in a browser’s DOM, there is no way of attaching this token to any request except a Javascript XMLHTTPRequest. In our product, we have a requirement to use specific subdomains for certain media delivery features, which are often included using HTML elements i.e. <video>, <audio> or <img>. In this scenario, we cannot attach the Authorization header using Javascript because the browser does not expose the ability to intercept requests from HTML elements in the DOM 👻.

We needed a solution in the browser which allowed us to ensure:

  • JWTs are sent with requests only when needed
  • We can choose to send JWTs with cross-origin requests initiated by HTML elements
  • The request is routed from the user to the service as directly as possible

Enter Service Workers

A Service Worker is a type of Web Worker that acts as a programmable network proxy for your website and lives in the user’s browser between the Document and the browser’s underlying network interface.

Service Workers are most commonly used to provide offline capabilities to sites, a vital part of the PWA standard. This is achieved by using Service Workers to cache responses in the browser and serve those cached responses when the browser can’t reach the internet.

This proxy behaviour can also be used to intercept and modify the headers of any HTTP requests initiated by the Document, including those from HTML tags such as <video> and <img>.

The Service Worker proxies API calls and attaches the JWT to them

Within this Worker, we can read the request’s URL and choose with Javascript whether or not to attach a JWT, so we also get granular control of the Authorization header.

Security Considerations

The Worker has to have access to the JWT, so it’s essential that a hacker cannot steal it via the Worker.

Service Workers are installed in the user’s browser, but the installation is limited in scope, so they only activate on the origin and path on which they are registered. External sites have no means of accessing the Worker’s state.

A Worker runs in a separate thread to the main thread, meaning that the Document has no access to its state or memory. The Document can only communicate with a Worker via the postMessage API. By controlling what data the Worker sends to the Document, you can prevent a Cross-Site Scripting (XSS) attack from stealing the token.

Caveats

Versioning and deployments

As a Worker is installed on a user’s device instead of being retrieved from your server on every request, delivering updates to the Worker code is different from a conventional web page. Therefore, you need to take care when shipping updates to the Worker, ensuring each version is backwards-compatible.

Non-HTTP protocols

Service Workers cannot intercept WebSocket initiation, so we had to implement a different AuthN mechanism for those. However, cookies can be sent with WS connections, so this is one area where cookies have an advantage.

Browser support

Some browsers such as Firefox have a setting that allows users to disable Service Workers. Without a fallback mechanism, the application won’t work for those users. Maintaining two security implementations increases risk, so we have to decide how to handle a scenario where Workers are unavailable.

Non-standard

This is a slightly unusual way of solving this issue and an unusual way of using Service Workers. However, using tried and tested methods are generally a safer security approach, as not every problem will be readily apparent. In contrast, potential issues will have been identified and accounted for in a more widely adopted paradigm.

Summary

Service Workers are a powerful tool with a wide variety of applications, but they should be used with care. Also, while this helps to address some of our requirements, we still require alternative implementations for specific use cases.

If not for the requirements around reducing latency, we would probably opt for the single Gateway approach. Services like AWS’ API Gateway, Lambda@Edge and paradigms like Graph APIs make this a compelling and flexible approach. However, these can be pretty expensive, especially at scale, unlike Service Workers, which run in the user’s browser. Furthermore, the cost of building and maintaining global edge infrastructure is high, compared to a Service Worker built by one developer in a day or two.

Interested in joining our engineering team? We’re always on the lookout for talented individuals – check out our open roles here.

We’re hiring!

We’re looking for talented engineers in all areas to join our team and help us to build the future of broadcast and media production.
Scroll to Top

Try Grabyo for free.

All we need is a few details from you, then a member of our team will be in touch shortly.

Book a call with us.

We just need a few details from you, then you will be able to book a meeting directly with a member of our team!

Try Grabyo for free.

All we need is a few details from you, then a member of our team will be in touch shortly.