Friday, December 30, 2016

Web App Design: Client - Server API

There are several ways to design a web application in terms of client-server architecture. We have learned one of them from the previous article. To summarize, we separate view templates from raw data so that we can access the same data using several different clients. The server only needs to deliver view templates and raw data as requested by the browser. It will be the browser’s job to put them together to make a usable UI.

While this design offers simplicity at the server side and flexibility at the client side, it does come at a cost. We now need a more elaborate server API (Application Programming Interface). This comes from the fact that the server needs to handle view and data request separately. We may need to define several functions to handle the data requests of even a single view. Overall, the client-server interaction will increase, and we need to design this thoughtfully to ensure the security, reliability, and performance of our web application.

Look who’s here!

Whether we choose to mix or separate view and data, a basic security principle applies. We need to authenticate and authorize users that access our web application. Authentication deals with validating the identity of a user (“who” is the user?). Authorization deals with enforcing access control for an authenticated user (“what” can the user do?). We must verify whether a request comes from an authenticated user. When it does, we must also verify whether that user is authorized to make such request. This applies to each and every request the server receive, whether it’s for a view or data. In other words, we must consider the authentication / authorization aspect of each service / function at the server.

A common way to implement authentication is using a token (session ID) delivered as HTTP Cookie. When a user successfully signed in, a session ID is generated and sent to the browser as a Cookie. For each subsequent requests, the browser will send this Cookie along with the request. The server will then use the session ID to verify whether the session is still valid, and also to determine “who” is making the request. After identifying the user, the server can then determine whether that user is allowed to access the requested view or data. To ensure better security, it’s advisable to mark the cookie as HTTP-only, and send it via HTTPS. This will reduce the risk of a session ID get “stolen” by a malicious script (XSS attack) or by a network sniffer (MITM attack).

State of the art

The next thing to consider is about the statefulness of the server. Before we talk about the difference between a stateful and stateless server, let’s talk about application states. The word “state” in an application can refer to one of two different meanings. The first is the interaction state. This state is short-lived (only valid during a single transaction), and usually stored in volatile memory (variables). The second one is the resource state. This state is long-lived (valid across different transactions), and usually stored in persistent storage (file / database). The state we’re interested in is the interaction state.

By being stateful, the server is responsible for tracking the interaction state of each client. This means that the server must “remember” what each user has been doing, or what data they have submitted for the current transaction. The saved state / data at the server will affect how the server will handle the next request. This means that the server must allocate some resource to save those states, and implement some kind of algorithm to manage them. This does not scale well with increased number of user or the service provided. It will impact the server in terms of complexity and the amount resource needed.

When a server is stateless, it will be the client’s responsibility to manage the interaction state. The server doesn’t care about the previous interactions / request-response cycles. It only cares about the data submitted by the client for the current request. Please remember that this is in the context of an interaction state. In the context of a resource state (file / database), the server still regards previously persisted states (saved files / committed database transactions). Resource state management falls under the 2nd-to-3rd tier communication, not the 1st-to-2nd tier we’re currently considering.

In a stateless server, the interaction state of the current transaction resides at the client side. The server knows the state of a certain client because that client send the state information along with a request. Therefore, the client must implement some algorithm and allocate some resource to manage its own state. The algorithm will be much simpler since it only handle the state of a single client. And since the amount of available resource is proportional to the number of clients, this model will scale better.

Face to face

When we’ve taken care of the security and state management aspect, it’s time to design our API. In general, we will have 2 types of service, one that returns a view template (HTML + CSS + Javascript), and one that returns data (JSON/XML). The view service API will be easier to design because it represents the UI that users interact with during a transaction. We can easily identify the necessary functions from the UI flow of a certain transaction. We can even implement a single function to handle all view requests of our web application, although this might not be a good idea. This is possible because all a view service needs to do is fetch the requested view template, and send it to the client.

The data service API on the other hand, is more complicated and needs a fine-grained design. This is because data services can vary in terms of returned values and parameters. For example, a data service may return a single value, an object, an array, etc. A value itself can be a string, number, date, binary, etc. A data service may also need parameters of various length and data types.

A good place to start is a list of user actions that result in a process requiring communications between the 2nd and 3rd tier. For example, a user clicks a search button to find a product. This would require a database access, and therefore must be handled by a server-side code. We could translate this to the server API as something like find_product function. Such function would require a part of the product name as a parameter, and return an object as the result. We could further define the returned object as having properties such as product code, product name, available qty, etc.

Another place where we can identify the necessity of a server API is when it involves some business logic or complex calculations. Implementing business logic at the server-side keeps our client-side lightweight. It also avoids code duplication when we decide to access the same server using several different client implementations (e.g. native mobile / desktop application).

All sewn up

Another important part of UI interaction is data validation. We can implement data validation at client-side, server-side, or both. The advantage of client-side validation is of course, responsiveness. This is because any calculation involved is done locally by the browser, and no network connection is necessary. The downside is, it’s not secure. We need to remember that the client side of a web application (HTML + CSS + Javascript) is basically “open source”. Of course we could minify and obfuscate things, but a determined user might still be able to decipher it and bypass any client-side validation we may have implemented. Therefore, it’s important to view any client-side data validation as a user experience enhancement rather than a security / data integrity measure.

Furthermore, there are cases where a validation involves the 3rd tier. In these cases, the validation must be done by the 2nd tier. For example, let’s consider an inventory application in which a user can add new products. We may need to check the database whether a product already exists, and inform the user accordingly. This would be one of the advantage of server-side validation, it has access to the 3rd tier. Another important advantage is security, since the code resides on the server. The downside would be latency and bandwidth consumption, since the client needs to send the data to the server, and wait for the server’s response to determine whether or not the data is valid.

Performing validation both client-side and server-side would be the best of both worlds. We can implement client-side validation for preliminary checking to show invalid input to the user. Again, this is only for convenience purpose, to allow a user to notice invalid input and fix it quickly instead of waiting for a request-response cycle. We still need to perform the same validation (and may even be more with databases, etc) at the server-side to ensure data integrity. Only after an input pass the client-side validation, will it be send to the server for server-side validation and further processing. This way we can achieve a good user experience without sacrificing data integrity.

No comments:

Post a Comment