Using Azure Front Door for Eliminating Preflight Calls (CORS)
You can use an Azure Front Door to route to both the UI domain and the API to eliminate the (OPTIONS) request; calls from the browser will be directed to the same-origin as the UI.
Join the DZone community and get the full member experience.
Join For FreeWhat 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 Works
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 the server has enabled resource sharing with the current domain. The browser makes a preflight call by sending an HTTP request with the 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 sent as part of the actual HTTP request.
Server Headers
Access-Control-Allow-Origins
: Origins that are allowed to access resources 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 the Browser Initiate Preflight?
If the request is to the same origin (even if it's complicated), the browser doesn't initiate a 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:
The HTTP Method is simple. Browsers consider
GET
,HEAD
, andPOST
to be simple.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.
The
Content-Type
header value is simple. The allowed values for Content-Type are:application/x-www-form-urlencoded
,multipart/form-data
andtext/plain
.
For any deviation from the above 3 points, the browser will make the preflight OPTIONS
for any cross-site request.
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 that 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/domains. 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 come into the picture; 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: www.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 frontend 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 to be a same-origin call; the browser won't make the preflight request.
Routing Configuration Conventions
Developers should follow some conventions to achieve this kind of setting.
Each backend should be identifiable by a unique routing rule.
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 backend has a unique name, making it convenient to configure the routing rules. Following Microsoft's REST API Guidelines should be a good start.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.If URLs are not enough to distinguish the backends, then complicated routing rules can be created using Rule Engines of Azure Front Door.
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
calls can reduce the API latency.
Published at DZone with permission of Pratik Bhattacharya. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments