Increasing the client-side expressiveness is risky
“In the beginning was the hyperlink, and the hyperlink was with the web, and the hyperlink was the web. And it was good.” -Anonymous
“In the beginning was the hyperlink, and the hyperlink was with the web, and the hyperlink was the web. And it was good.” -Anonymous
Modern web development demands heavy client-side processing logic. The days of simple HTML forms with the Submit button seem like a distant memory; nowadays, end-users expect feature-rich functionality available in their browsers.
Sounds great, right? Yes, but what about that pesky little web browser? It’s a well-known fact that all web browsers are notorious for being untrusted computing environments.
What do we mean by ‘non-trusted computing environment’?
If we are to enable client-side expressiveness (i.e. enable front-end developers to implement sophisticated and elaborate processing of business policy rules), we must give developers advanced, powerful tools. So, what’s wrong with that? Giving sophisticated, powerful tools to developers sound like a great way to improve quality and productivity.
True. However, the problem is that giving those tools to our developers also means we are giving the same tools to potentially hostile end-users (i.e., bad actors).
How is that possible? Well, the inherently non-trusted computing environment, such as a web browser, opens the possibility of easily hacking into our system and exploiting various loopholes. Web browser is a very permissive application which allows anyone to inspect and examine what is going on under the hood.
How does client-side processing logic work?
Front-end applications need to request values that are stored on the back end. Then, once the requested values are delivered in the response, the front-end app displays the values to the end user and awaits further action initiated by the user. The user can make some modifications to the displayed values and then request that those modifications be processed and stored on the back end. The front-end app accomplishes that by sending the request back to the server.
In the early days of web apps, the above described processing logic on the client was implemented using plain vanilla HTML. Soon after the e-commerce revolution picked up, it became obvious that such static, pedestrian way of processing business policy rules was somewhat insufficient and was deemed inadequate. The client-side apps had to be enhanced by some custom processing logic. Enter JavaScript in the browser.
With JavaScript running in the browser, suddenly the sophistication levels of the front-end apps increased. The old thin client/fat server model got turned on its head and we started building elaborate fat client/thin server models. A Single Page App (SPA) was born. Endless scrolling. Desktop-like user experience in the browser.
What do we mean by ‘client-side expressiveness’?
The easiest way to illustrate client-side expressiveness is to look at a typical client application that enables users to interact with a back-end database. Upon logging into such client application and obtaining necessary clearance, users find themselves in the position where they can express various intents to manipulate the state of the back-end database. They can freely query the database, they can update the existing data, they can insert new data into the database. They can also delete any data from the database.
This client-side expressiveness is endowing users with superpowers. Typically, only select group of privileged users (i.e., database administrators) would ever be given such client-side expressiveness superpowers. Regular users must never get access to such levels of expressiveness.
Where is the problem?
Unlike desktop apps, which execute the processing logic using compiled, binary code that is not publicly available (nor is it easily scrutinized), web apps (SPAs) execute the processing logic using the scripted code that is publicly available. Being publicly available, it becomes exploitable. Security therefore gets easily compromised.
For example, let’s examine a situation where front-end developers are collaborating with back-end developers who are building APIs. Front-end dev notices: “This page needs customer id and full name; could you please create an API end-point with the response format {first_name; last_name}, given the value of customer_id?”
The API developers oblige and enable the client to send the query that specifies customer id and returns customer full name. Like so:
{
customer(id: 123456789) {
first_name,
last_name
}
}
This expressive power given to the front-end developer is also the expressive power given to the end-users who may be keen on exploring those powers. Some of those end-users could potentially be hostile users. It is difficult to imagine a computing environment that could be less secure than a web browser. Which means that expressive front-end development platforms create a field day for malicious/hostile end-users/bad actors.
This creates a huge problem: almost anything our front-end developer can do, bad actors can do as well. A hostile user can easily figure out the API by inspecting HTTP Requests (in general, a very easy task not requiring advanced reverse engineering skills). Once the API has been grasped, hostile user can doctor the above query as follows:
{
customer(id: 123456789) {
first_name,
last_name,
credit_card_number
}
}
Ouch! Even if the end-user is not hostile (could be merely curious), the properly secured system should never allow such query to be successfully executed.
But what if it becomes a legitimate query? What if the business policy rule changes and is now allowing for the query to return the credit card number? How does the system prevent users from prying into other users’ credit card numbers?
The only way to assure proper security is to implement client-side processing logic that is sensitive to the field-level security. The system processing the query must know who is sending the query and what exactly is being asked. That may sound simple at first but knowing how typically even a regular business rule could involve a lot of data elements/fields, things quickly mushroom and become exceedingly complicated. Before we know it, we have created breeding ground for bugs.
Conclusion
The more power is given to the client-side UI developers, the more security headaches get introduced to worry about. New developments in the client-side technology (GraphQL, gRPC, etc.) offer amazing expressive powers to front-end developers. But with that power comes huger risk as well as huge responsibility. The power tools modern front-end developers have at their disposal are a veritable double-edged sword. That sword cuts both ways — enables developers to build amazing client-side apps while those power tools at the same time open a can of security worms.
One way to gain insight into the dangers of having such power tools running inside the browser is to compare it to the situation where users could send SQL queries directly to the back-end database from the web browser. We have extremely valid reason to make such technology illegal in the web browsers, so how come we don’t have the same or similar concerns with GraphQL, gRPC, and other power tools of the same ilk?
From this analysis we see that it is urgently necessary to curb client-side expressiveness.