Was this Helpful?

This topic discusses adding CORS (Cross-origin resource sharing) support, including preflight support, to an API proxy. 

Adding CORS support to an API proxy

CORS (Cross-origin resource sharing) is a standard mechanism that allows JavaScript XMLHttpRequest (XHR) calls executed in a web page to interact with resources from non-origin domains. CORS is a commonly implemented solution to the "same-origin policy" that is enforced by all browsers. For example, if you make an XHR call to the Twitter API from JavaScript code executing in your browser, the call will fail. This is because the domain serving the page to your browser is not the same as the domain serving the Twitter API. CORS provides a solution to this problem by allowing servers to "opt-in" if they wish to provide cross-origin resource sharing. 

Most modern browsers support CORS. You can find a comprehensive list of supported browsers here. For an in-depth description of CORS, see the Cross-Origin Resource Sharing W3C Recommendation.

Typical use case for CORS

The following JQuery code calls the Yahoo Weather API. If executed from within the context of a browser (a web page), the call will fail because of the same-origin policy:

<script>
var url = "http://weather.yahooapis.com/forecastrss?w=12797282";
$(document).ready(function(){
  $("button").click(function(){
    $.ajax({
        type:"GET",
        url:url,
        async:true,
        dataType: "xml",
           success: function(xml) {
              // Parse the response.
              // Do other things.
           },
           error: function(xhr, status, err) {
              // This is where we end up! 
            }
    });
  });
});
</script>

One solution to this problem is to create an Apigee API Services proxy that calls the Yahoo API on the back end. Remember that API Services sits between the client (a browser in this case) and the backend API (Yahoo Weather). Because the API proxy executes on the server, not in a browser, it is able to call Yahoo Weather successfully. Then, all you need to do is attach CORS headers to the TargetEndpoint response. As long as the browser supports CORS, these headers signal to the browser that it's okay to "relax" its same-origin policy, allowing the cross-origin API call to succeed.

Once the proxy with CORS support is created, you can call the API proxy URL instead of the backend service in your client-side code. For example:

<script>
var url = "http://myorg-test.apigee.net/v1/my-weather-api/forecastrss?w=12797282";
$(document).ready(function(){
  $("button").click(function(){
    $.ajax({
        type:"GET",
        url:url,
        async:true,
        dataType: "xml",
           success: function(xml) {
              // Parse the response.
              // Do other things.
           },
           error: function(xhr, status, err) {
              // This time, we do not end up here!
            }
    });
  });
});
</script>

In the simplest case, you can return CORS headers to the client and the cross-origin request will work. More complicated cases exist where a "preflight" request is required. You can read about preflight CORS requests in Cross-Origin Resource Sharing W3C Recommendation, as well as in numerous articles and blogs. See also "Handling CORS preflight requests" below.

Attaching an Add CORS policy to a new API proxy 

You can add CORS support to an API proxy by attaching an "Add CORS" policy to the API proxy when you create it. To add this policy, select the Browser Access checkbox in the Add API Proxy dialog, as shown in the following figure. 


 

When you select this checkbox, a policy called Add CORS is automatically added to the system and attached to the TargetEndpoint response preflow, as shown in the following figure:
 


 

The Add CORS policy is implemented as an AssignMessage policy, which adds the appropropriate headers to the response. Here is the XML for the Add CORS policy:

<AssignMessage async="false" continueOnError="false" enabled="true" name="Add-CORS">
    <DisplayName>Add CORS</DisplayName>
    <FaultRules/>
    <Properties/>
    <Add>
        <Headers>
            <Header name="Access-Control-Allow-Origin">*</Header>
            <Header name="Access-Control-Allow-Headers">origin, x-requested-with, accept</Header>
            <Header name="Access-Control-Max-Age">3628800</Header>
            <Header name="Access-Control-Allow-Methods">GET, PUT, POST, DELETE</Header>
        </Headers>
    </Add>
    <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
    <AssignTo createNew="false" transport="http" type="response"/>
</AssignMessage>

Basically, the headers let the browser know which origins it will share its resources with (in this case "all origins"), which methods it accepts, and so on. You can read more about these CORS headers in the Cross-Origin Resource Sharing W3C Recommendation.

Adding CORS headers to an existing proxy

You need to manually create a new Assign Message policy and copy the code for the Add CORS policy listed in the previous section into it. Then, attach the policy to the response preflow of the TargetEndpoint of the API proxy. You can modify the header values as needed. For more information on creating and attaching policies, see Policy attachment and enforcement.

Handling CORS preflight requests

CORS preflight refers to sending a request to a server to verify if it supports CORS. Typical preflight responses include which origins the server will accept CORS requests from, a list of HTTP methods that are supported for CORS requests, headers that can be used as part of the resource request, the maximum time preflight response will be cached, and others. If the service does not indicate CORS support or does not wish to accept cross-origin requests from the client's origin, the cross-origin policy of the browser will be enforced and any cross-domain requests made from the client to interact with resources hosted on that server will fail.

Typically, CORS preflight requests are made with the HTTP OPTIONS method. When a server that supports CORS receives an OPTIONS request, it returns a set of CORS headers to the client that indicate it's level CORS support. As a result of this handshake, the client knows what it is allowed to request from the non-origin domain. 

For more information on preflight, refer to the Cross-Origin Resource Sharing W3C Recommendation. There are in addition numerous blogs and articles on CORS that you can refer to.

Apigee does not include a CORS preflight solution out of the box, but it is possible to implement, as described in this section. The objective is for the proxy to evaluate an OPTIONS request in a conditional flow. The proxy can then an appropriate response to send back to the client. 

Let's look at a sample flow, and then discuss the parts that handle the preflight request:
 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ProxyEndpoint name="default">
    <Description/>
    <Flows>
        <Flow name="OptionsPreFlight">
            <Request/>
            <Response>
                <Step>
                    <Name>Add-CORS</Name>
                </Step>
            </Response>
        <Condition>request.verb == "OPTIONS"</Condition> 
        </Flow>
    </Flows>

    <PreFlow name="PreFlow">
        <Request/>
        <Response/>

    </PreFlow>
    <HTTPProxyConnection>
        <BasePath>/v1/cnc</BasePath>
        <VirtualHost>default</VirtualHost>
        <VirtualHost>secure</VirtualHost>
    </HTTPProxyConnection>
    <RouteRule name="NoRoute">
        <Condition>request.verb == "OPTIONS"</Condition>
    </RouteRule>
    <RouteRule name="default">
        <TargetEndpoint>default</TargetEndpoint>
   </RouteRule>
   <PostFlow name="PostFlow">
        <Request/>
        <Response/>
    </PostFlow>
</ProxyEndpoint>

The key parts of this ProxyEndpoint are as follows:

  • A RouteRule is created to a NULL target with a condition for the OPTIONS request. Note that there is no TargetEndpoint specified. If the OPTIONS request is received, the proxy immediately returns the CORS headers in a response to the client (bypassing the actual default "backend" target).  For details on flow conditions and RouteRule, see Flow variables and conditions.

    <RouteRule name="NoRoute">
        <Condition>request.verb == "OPTIONS"</Condition>
    </RouteRule>
    
  • An OptionsPreFlight flow is created that adds an Add CORS policy, containing the CORS headers, to the flow if an OPTIONS request is received. 

     <Flow name="OptionsPreFlight">
                <Request/>
                <Response>
                    <Step>
                        <Name>Add-CORS</Name>
                    </Step>
                </Response>
            <Condition>request.verb == "OPTIONS"</Condition> 
     </Flow>

RouteRules are evaluated in the order specified in the ProxyEnpoint configuration. You should always have the default (no condition) Route at the end. Otherwise, if at the top, it will always match and never evaluate the other Route possibilities.

Help or comments?

  • Something's not working: See Apigee Support
  • Something's wrong with the docs: Click Send Feedback in the lower right.
    (Incorrect? Unclear? Broken link? Typo?)