Apigee Pre-Flight Options Requests

Not applicable

I create api proxies and check the box that says "Enable Direct Browser Access for Your API — Allow direct requests from a browser via CORS." but my OPTIONS requests are still failing with :

{
    "fault": {
        "faultstring": "Received 405 Response without Allow Header",
        "detail": {
            "errorcode": "protocol.http.Response405WithoutAllowHeader"
        }
    }
}	

From what I understand about CORS Pre-Flight Options requests, the client first sends the OPTIONS request to the server as a safeguard for "safe" CORS. This request should return a response with the list ofrequest types that are available.

My Question: How do I make it so that Apigee responds correctly to OPTIONS requests and does not pass the OPTIONS request to my api behind the proxy?. If it helps I have AngularJS javascript apps trying to communicate with my Apigee endpoint.

Javascript errors:

OPTIONS http://api.example.com No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://client.example.com' is therefore not allowed access.

XMLHttpRequest cannot load http://api.example.com No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://client.example.com' is therefore not allowed access. 


Default "Add CORS" xml

<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>

Default Proxy Endpoints xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ProxyEndpoint name="default">
    <Description/>
    <Flows/>
    <PreFlow name="PreFlow">
        <Request/>
        <Response/>
    </PreFlow>
    <HTTPProxyConnection>
        <BasePath>/v1/cnc</BasePath>
        <VirtualHost>default</VirtualHost>
        <VirtualHost>secure</VirtualHost>
    </HTTPProxyConnection>
    <RouteRule name="default">
        <TargetEndpoint>default</TargetEndpoint>
    </RouteRule>
    <PostFlow name="PostFlow">
        <Request/>
        <Response/>
    </PostFlow>
</ProxyEndpoint>

Default Target Endpoint xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<TargetEndpoint name="default">
    <Description/>
    <Flows/>
    <PreFlow name="PreFlow">
        <Request/>
        <Response>
            <Step>
                <Name>Add-CORS</Name>
            </Step>
        </Response>
    </PreFlow>
    <HTTPTargetConnection>
        <URL>http://api.example.com/v1/assets.json</URL>
    </HTTPTargetConnection>
    <PostFlow name="PostFlow">
        <Request/>
        <Response/>
    </PostFlow>
</TargetEndpoint>
Solved Solved
4 6 11.3K
1 ACCEPTED SOLUTION

Not applicable

Since you don't want the OPTIONS request to pass through to the backend API, there are two things needed:

  1. A RouteRule to a null target with condition for the OPTIONS request. Notice there is no TargetEndpoint specified.
  2. A custom flow in the ProxyEndpoint to handle the CORS response. Since the new RouteRule sends the message to a null Target (echoes request back to client), the message will not route to the 'default' TargetEndpoint where the CORS policy currently is defined.

The route rule would look as follows:

<RouteRule name="NoRoute">
    <Condition>request.verb == "OPTIONS"</Condition>
</RouteRule>

An updated version of the ProxyEndpoint would look as follows:

<?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>

NOTE: The 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.

For now, you'd have to make the change any time you create a new proxy from the UI and need the full CORS support. Apigee needs to enhance the new API proxy builder to address this issue.

After this is resolved, folks may also run into: XMLHttpRequest cannot load <url>. The 'Access-Control-Allow-Origin' header contains the invalid value 'http://localhost:9090, *'. Origin 'http://localhost:9090' is therefore not allowed access. As outlined inaspnetwebstack.codeplex.com/workitem/1539 ... this happens when two "Access-Control-Allow-Origin" headers are getting added to the response. To resolve this, remove Add-CORS step from the TargetEndpoint's PreFlow's Response

View solution in original post

6 REPLIES 6

Not applicable

Since you don't want the OPTIONS request to pass through to the backend API, there are two things needed:

  1. A RouteRule to a null target with condition for the OPTIONS request. Notice there is no TargetEndpoint specified.
  2. A custom flow in the ProxyEndpoint to handle the CORS response. Since the new RouteRule sends the message to a null Target (echoes request back to client), the message will not route to the 'default' TargetEndpoint where the CORS policy currently is defined.

The route rule would look as follows:

<RouteRule name="NoRoute">
    <Condition>request.verb == "OPTIONS"</Condition>
</RouteRule>

An updated version of the ProxyEndpoint would look as follows:

<?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>

NOTE: The 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.

For now, you'd have to make the change any time you create a new proxy from the UI and need the full CORS support. Apigee needs to enhance the new API proxy builder to address this issue.

After this is resolved, folks may also run into: XMLHttpRequest cannot load <url>. The 'Access-Control-Allow-Origin' header contains the invalid value 'http://localhost:9090, *'. Origin 'http://localhost:9090' is therefore not allowed access. As outlined inaspnetwebstack.codeplex.com/workitem/1539 ... this happens when two "Access-Control-Allow-Origin" headers are getting added to the response. To resolve this, remove Add-CORS step from the TargetEndpoint's PreFlow's Response

It worked for me also. Thanks a lot 🙂

Not applicable

@mmalloy @mmalloyapigeecom Thanks for this, it is really useful information. I am attempting to do a very similar request but am getting stuck on The 'Access-Control-Allow-Origin' header contains multiple values. I have removed the add-cors from the TargetEndpoints Preflow Response but not having much luck.

I am new to using Apigee so hopefully it is just something really simple I am missing!

Cheers,

jovaniac
Participant II
hey guys, I implemented something like that and it served me correctly.
In the proxy enpoint we must place in the preflow the next call of a Flowcallout to invoke a sharedflow which will have the policy of CORS

<PreFlow name="PreFlow">
<Request>
<Step>
<Name>FC-CORS</Name>
</Step>
<Step>
<Name>FC-OAuth2</Name>
</Step>
</Request>
<Response/>
</PreFlow>

Definition of flowcallout, where we invoke the sharedflow

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<FlowCallout async="false" continueOnError="false" enabled="true" name="FC-CORS">
<DisplayName>FC-CORS</DisplayName>
<FaultRules/>
<Properties/>
<SharedFlowBundle>OPTIONS-CORS-Headers-Response</SharedFlowBundle>
</FlowCallout>

definition of sharedflow

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<SharedFlow name="default">
<Step>
<Name>OPTIONS-CORS-Headers-Response</Name>
<Condition>request.verb == "OPTIONS"</Condition>
</Step>
</SharedFlow>

definition of the policy of raisefull, where we will indicate the headers of Access-Control-Allow-Origin with * that will allow the invocation from our browser

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<RaiseFault async="false" continueOnError="false" enabled="true" name="OPTIONS-CORS-Headers-Response">
<DisplayName>OPTIONS CORS Headers Response</DisplayName>
<Properties/>
<FaultResponse>
<Set>
<Headers>
<Header name="Access-Control-Allow-Origin">*</Header>
<Header name="Access-Control-Allow-Headers">origin, x-requested-with, accept, ucsb-api-key, ucsb-api-version, authorization</Header>
<Header name="Access-Control-Max-Age">3628800</Header>
<Header name="Access-Control-Allow-Methods">GET, PUT, POST, DELETE</Header>
</Headers>
<Payload contentType="text/plain"/>
<StatusCode>200</StatusCode>
<ReasonPhrase>OK</ReasonPhrase>
</Set>
</FaultResponse>
<IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
</RaiseFault>

Regars

Hey I am still not getting access-control-allow-origin in my response header for the preflight request after following the tutorial here, can someone help me understand what mistake I made with my setup?

Here is my add-cors

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<CORS continueOnError="false" enabled="true" name="add-cors">
  <DisplayName>Add CORS</DisplayName>
  <AllowOrigins>*</AllowOrigins>
  <AllowMethods>GET, PUT, POST, DELETE, OPTIONS</AllowMethods>
  <AllowHeaders>Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers, Authorization</AllowHeaders>
  <ExposeHeaders>*</ExposeHeaders>
  <MaxAge>3628800</MaxAge>
  <AllowCredentials>false</AllowCredentials>
  <GeneratePreflightResponse>true</GeneratePreflightResponse>
  <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
  <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>
</CORS>

 

 Here is my proxy endpoint with OptionsPreflight at the front

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ProxyEndpoint name="default">
  <Flows>
    <Flow name="OptionsPreFlight">
      <Request>
        <Step>
          <Name>add-cors</Name>
        </Step>
      </Request>
      <Response/>
      <Condition>request.verb == "OPTIONS" AND request.header.origin != null AND request.header.Access-Control-Request-Method != null</Condition>
    </Flow>
  </Flows>

 

Here is my no-route for options

 

  <RouteRule name="NoRoute">
    <Condition>request.verb == "OPTIONS" AND request.header.origin != null AND request.header.Access-Control-Request-Method != null</Condition>
  </RouteRule>
  <RouteRule name="default">
    <TargetEndpoint>default</TargetEndpoint>
  </RouteRule>

 

I also see 

CORSResponseOrErrorFlowExecution in my debug session. Please help me out here, thank you so much!

The prior discussion that you are responding to here, is from 2014, and is well out of date. The CORS policy that you showed, which works in Apigee X and hybrid, should not be attached to a conditional flow.  It should be attached to the proxy preflow. Also it has no Add element - not sure where you got that.

I published a screencast on this previously.

In short, both the configuration of the CoRS policy, and the way you attach it into the API proxy endpoint, look wrong. 

Check the documentation, or the screencast. or both!