• Pratik Bhattacharya

Using Azure Front Door for Eliminating preflight calls (CORS)

Updated: Mar 7

What is CORS

Cross-Origin-Resource-Sharing (CORS) is a security mechanism built-in most modern browsers to restrict accessing resources from a server hosted on a different Domain. Using CORS techniques, servers can limit the sharing of data to only trusted Domains. Assume you have a frontend website hosted on https://www.contoso.com. Some JavaScript code from your website is trying to access an API hosted on https://www.foobar.com. This is an example of Cross-Origin resource sharing because the requesting domain and the resource domain are different. https://www.foorbar.com might have some sensitive information that it can share with only specific trusted domains, then https://www.foobar.com can specify a CORS policy where it can block requests from other Domains than the trusted sites. If the request originates from the same Domain, then CORS doesn't come into play.

How CORS work

When JavaScript from the frontend initiates an AJAX call, the browser will need to check if Server has enabled CORS. In such a situation instead of sending the actual request, the browser will make a 'preflight' request to the Server to determine if Server has enabled resource sharing with the current Domain. The browser makes a preflight call by sending an HTTP request with OPTIONS method to the origin containing the resource. The HTTP options call will also send the current origin, non-standard headers, and the original HTTP call method.

Based on this information, the Server can take a decision. Suppose the Server can accept the given call. In that case, it will send a response code of 204 along with headers specifying the origins from which requests are allowed, allowed HTTP methods, allowed custom headers and cache duration.

If the Server sends a response of 204, allowed origins contain the current origin, and the headers and methods that the client wants to send are present in the server response, then the browser enables the actual request to go through. For any other cases, the browser will block the request.

Client Headers

  • Access-Control-Request-Method: HTTP Method of the actual HTTP request.

  • Access-Control-Request-Headers: Headers that will be send as part of the actual HTTP request.

Server Headers

  • Access-Control-Allow-Origins: Origins that are allowed to access resource from the server. '*' can be placed to indicate that all origins are allowed.

  • Access-Control-Allow-Methods: HTTP Methods which are allowed by the server.

  • Access-Control-Allow-Headers: Headers that the client can send to the server while accessing Servers.

  • Access-Control-Max-Age: Seconds for which the client can cache this response. For a similar request from the client, the browser need not send another preflight request to the server for the given duration.

When does Browser initiates Pre-Flight

If the request is to the same origin (even if it's complicated), the browser doesn't initiate preflight OPTIONS request. For cross-site requests, the browser forgoes CORS if the request is "simple". For an HTTP request to be "simple", the request must guarantee the following conditions:

  1. The HTTP Method is simple. Browsers consider GET, HEAD and POST to be simple

  2. Only standard headers are present in the HTTP Request. Headers set automatically by the user agent are considered to be simple. To see a list of standard headers click here.

  3. Content-Type header value is simple. The allowed values for Content-Type are application/x-www-form-urlencoded, multipart/form-data and text/plain.

For any deviation from the above 3 points, the browser will make the preflight OPTIONS for any cross-site request.

To learn more about CORS click here.

Performance Penalty

From a security perspective, CORS is essential. However, there is a noticeable performance penalty. Due to the initial OPTIONS call, the actual request is stalled by the browser until the Server can respond to the preflight request. Based on the time, the browser takes to respond to the preflight request; the App will have to wait for data to load from the Server. Although the Server doesn't have to perform a lot of processing, various factors like SSL handshaking, regional latency, network jitters, server-side load, client-side load, etc. all contribute to the performance penalty. In some cases, OPTIONS requests can take up to 1-2s based on the factors mentioned above.

In an enterprise scenario where multiple teams have their apps deployed in different services keeping a common domain may not be feasible. All these services and APIs belonging to the same enterprise are trusted, but since they do not have the same Domain, the browser will initiate a preflight request for these requests.

We can leverage Azure Front Door as an ingress controller to eliminate the OPTIONS request.

Azure Front Door

It's a Level 7 Load balancer which uses anycast protocol with split TCP and Microsoft's global network. It's global and highly scalable.

You can configure any internet-facing service (can be hosted outside Microsoft Azure) as a backend to an Azure Front Door. You can also configure routing rules to route incoming client requests to the most suitable backend (based on availability and speed).

To learn more about Azure Front Door click here.

Configuring Azure Front Door for CORS elimination

Each front door can have one or multiple frontends/domain. A front door can have multiple internet-facing backends configured. You can now access all these backends via one of the front door domains. A common domain can now access multiple internet-facing services, each hosted on various services having different Domains. Extending the same idea, we can also host the frontend application to the same Front door and access it via the same Domain. The problem with this approach would be, how will Azure Front Door know which backend to direct an incoming request. This is where routing rules in Azure Front Door comes into picture where we can configure routing rules based on the incoming request URL and other parameters to redirect them to the required backend.

In the above examples, two services - www.profiles.azurewebsites.net and www.pos.com have been configured as backends to the front door with Domain - ww.ep.com. The routing rule has been configured to redirect request with URL /sales/* to www.pos.com and requests with URL /api/user/* to www.profiles.azurewebsites.net.

If we configure the frontend UI web app as another backend, you can access all the APIs and the UI via the same Domain. When a request goes from the UI to any configured APIs, the browser will request the same Domain. Hence the browser won't initiate the CORS preflight, allowing you to save precious millisecond to make your website more responsive.

In this example, the front end application is hosted on www.app.azurewebsites.net and is configured as another backend to the common Azure Front Door.

So both the APIs and the frontend web app can reach via the same Domain (www.ep.com)

If the web app needs to get the sales report instead of calling www.pos.com/sales/2021/report, it will call www.ep.com/sales/2021/report. When www.ep.com (Azure Front Door) receives the request, it will follow its routing rules and redirect the request to www.pos.com, giving the required data to the web app. Since the HTTP request originated from www.ep.com to www.ep.com, the browser will consider this be same-origin call. Hence the browser won't make the preflight request.

Routing Configuration Conventions

Developers should follow some conventions to achieve this kind of setting.

  1. Each backend should be identifiable by a unique routing rule.

  2. Follow proper HTTP guidelines and naming conventions while deciding routes and resources. Although it's not mandatory, using proper REST API guidelines to name the APIs can make this configuration work a lot easier. As per appropriate standards, each API should have an appropriate name of the resource in its route. So if a server is returning user profiles, then its route should be api/users/{user_name}. This will ensure that each API backends have a unique name, making it convenient to configure the routing rules. Following Microsoft's REST API Guidelines should be a good start.

  3. When a user visits ep.com, Azure Front Door should redirect the request to the Web App backend. Keep the Web App route as *. If none of the routing rules matches then Azure Front Door will redirect to the Web App.

  4. If URLs are not enough to distinguish the backends, then complicated routing rules can be created using Rule Engines of Azure Front Door.

Interested readers can see an working example here.

From the field

We used this technique in an Enterprise application and after a week of experimentation we observed the below result

It provides a clear indication that eliminating cross domain preflight OPTIONS call can reduce the API latency.

2,155 views0 comments

Recent Posts

See All