The Cross-Origin Resource Sharing (CORS) filter allows Repose to manage CORS requests without the origin service needing to understand CORS. For an introduction to CORS, see the CORS Overview section below.

Before enabling the CORS filter, you should be familiar with the CORS specification and the associated security implications. Enabling CORS can increase an endpoint’s exposure. Configuring CORS improperly could expose Cross-Site Request Forgery (CSRF) vulnerabilities in the origin service that were previously hidden by not supporting CORS.

General Filter Information

  • Name: cors

  • Released: v7.2.0.0

  • Bundle: repose-filter-bundle

  • Default Configuration: cors.cfg.xml

  • Configuration Schema: cors-configuration.xsd

Prerequisites & Postconditions

Required Request Headers

The presence and values of certain headers will indicate what kind of request is being made. All of these headers will be supplied by the client making the request.

Non-CORS Request Preflight CORS Request Actual CORS Request

None required

  • Origin

  • Access-Control-Request-Method

  • Origin*

* If the Origin header matches the original Host header in the request, it will be considered a non-CORS request. See the Origin Header section for more details.

Required Preceding Filters

The CORS filter should be one of the first filters, if not the first filter, in the filter chain in order to properly handle CORS Preflight Requests and to properly handle exposing response headers in CORS Actual Requests. If you want to rate limit CORS Preflight Requests, you can add the following filters before the CORS filter in the filter chain:

This Rate Limiting filter would be in addition to any Rate Limiting filter you may already have in place. The first Rate Limiting filter would filter only by IP address, and the second Rate Limiting filter would continue to rate limit the way it’s currently set up.

Request Headers Created

This filter does not create/modify any request headers.

Request Body Changes

This filter does not modify the request body.

This filter is not strictly required by any other filters.

Response Body Changes

This filter does not modify the response body, but it will set it in certain early termination conditions. See the [Response status codes] section for more details.

Response Headers Created

The filter will add the following headers to the response when the request is allowed to proceed:

Non-CORS Request CORS Preflight Request CORS Actual Request

None

  • Access-Control-Allow-Credentials

  • Access-Control-Allow-Origin

  • Access-Control-Allow-Headers

  • Access-Control-Allow-Methods

  • Access-Control-Allow-Credentials

  • Access-Control-Allow-Origin

  • Access-Control-Expose-Headers

The following headers will be added to the response when the request is rejected by this filter:

Origin is not allowed Method is not allowed Origin header is malformed

None

  • Access-Control-Allow-Origin

None

Response Status Codes

These are the conditions in which the CORS filter will stop processing the request and immediately return a response:

Response Code Response Body Request Type Reason

200

None

Preflight Request

Origin is allowed

403

None

Preflight Request

Origin is not allowed

403

None

Preflight Request

Method is not allowed

400

Bad Origin header

Actual Request

Origin header is malformed

403

None

Actual Request

Origin is not allowed

403

None

Actual Request

Method is not allowed

Examples

Basic Example

A basic CORS configuration would allow all origins to use any standard HTTP method on all resources.

If you configure an origin regex of .* or any other regex that could allow untrusted origins, you may want to consider securing your API from CSRF exploits.

cors.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<cross-origin-resource-sharing
    xmlns="http://docs.openrepose.org/repose/cross-origin-resource-sharing/v1.0">

    <allowed-origins>
        <origin regex="true">.*</origin> (1)
    </allowed-origins>

    <allowed-methods> (2)
        <method>OPTIONS</method>
        <method>GET</method>
        <method>HEAD</method>
        <method>POST</method>
        <method>PUT</method>
        <method>DELETE</method>
        <method>TRACE</method>
        <method>CONNECT</method>
    </allowed-methods>
</cross-origin-resource-sharing>
1 Allow all origins.
2 Allow these HTTP methods on all resources.

Limit Origins

To limit which origins are allowed to initiate a CORS request to your API, you can specify a literal value or a regular expression that the Origin header must match in order to proceed with the request.

cors.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<cross-origin-resource-sharing
    xmlns="http://docs.openrepose.org/repose/cross-origin-resource-sharing/v1.0">

    <allowed-origins>
        <origin>https://subdomain.other-domain.com:8443</origin> (1)
        <origin regex="true">http://.*.subdomain.rackspace.com(:\d*)?</origin> (2)
        <origin regex="true">http://www.openrepose.org(:80)?</origin> (3)
    </allowed-origins>

    <allowed-methods> (4)
        <method>GET</method>
        <method>POST</method>
    </allowed-methods>
</cross-origin-resource-sharing>
1 Allow this specific origin.
2 Allow any subdomain of ".subdomain.rackspace.com" on any port.
3 Allow this specific origin and support both web browsers that will include the default port and those that will leave it out.
4 Allow HTTP methods GET and POST on all resources.

Limit Methods By Resource

If specific resources support additional HTTP methods, you can configure this per-resource using a regex to specify the path or paths. The resource configuration is processed in the configured order, so the first path regex to match the request URI will be used in conjunction with the global allowed-methods configuration. This is used to determine the complete list of allowed methods to return in response to a CORS Preflight Request and to determine whether or not a CORS Actual Request is allowed to proceed past this filter.

This is not a substitution for authorization. Requests that do not contain the Origin header are not CORS requests and completely bypass this validation.

cors.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<cross-origin-resource-sharing
    xmlns="http://docs.openrepose.org/repose/cross-origin-resource-sharing/v1.0">

    <allowed-origins>
        <origin regex="true">.*</origin>
    </allowed-origins>

    <allowed-methods> (1)
        <method>GET</method>
        <method>HEAD</method>
    </allowed-methods>

    <resources>
        <resource path="/v1/status.*"/> (2)

        <resource path="/v1(/.*)?"> (3)
            <allowed-methods>
                <method>POST</method>
                <method>PUT</method>
            </allowed-methods>
        </resource>

        <resource path="/.*"> (4)
            <allowed-methods>
                <method>POST</method>
                <method>PUT</method>
                <method>PATCH</method>
                <method>DELETE</method>
            </allowed-methods>
        </resource>
    </resources>
</cross-origin-resource-sharing>
1 Allow HTTP methods GET and HEAD on all resources.
2 The /v1/status endpoint doesn’t support anything other than GET and HEAD.
3 The rest of /v1 supports POST and PUT in addition to GET and HEAD.
4 All other non /v1 endpoints support POST, PUT, PATCH, and DELETE in addition to GET and HEAD.

Using this configuration, you would see the following behavior:

Request URI Matched Path Access-Control-Allow-Methods

/v1/status/servers

/v1/status.*

GET, HEAD

/v1/status?status=destroyed

/v1/status.*

GET, HEAD

/v1/servers

/v1(/.*)?

GET, HEAD, POST, PUT

/v1

/v1(/.*)?

GET, HEAD, POST, PUT

/v2/servers

/.*

GET, HEAD, POST, PUT, PATCH, DELETE

/index.html

/.*

GET, HEAD, POST, PUT, PATCH, DELETE

Putting It All Together

This is an example configuration with notes on all of the required and optional elements and attributes.

cors.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<cross-origin-resource-sharing
    xmlns="http://docs.openrepose.org/repose/cross-origin-resource-sharing/v1.0">

    <allowed-origins>
        <origin>https://subdomain.other-domain.com:8443</origin>  (1) (2)
        <origin regex="true">http://.*.subdomain.rackspace.com(:\d*)?</origin> (3)
    </allowed-origins>

    <allowed-methods>
        <method>GET</method> (4)
        <method>HEAD</method>
    </allowed-methods>

    <resources> (5)
        <resource path="/v1/status.*"/>  (6) (7)

        <resource path="/v1(/.*)?">
            <allowed-methods> (8)
                <method>POST</method> (9)
                <method>PUT</method>
            </allowed-methods>
        </resource>

        <resource path="/.*">
            <allowed-methods>
                <method>POST</method>
                <method>PUT</method>
                <method>PATCH</method>
                <method>DELETE</method>
            </allowed-methods>
        </resource>
    </resources>
</cross-origin-resource-sharing>
1 At least one origin element is required.
2 The regex attribute is not required. If it is not present, it is defaulted to false and the specified URI is treated as a literal string.
3 When the regex attribute is true, the specified URI will be treated as a Java regular expression.
4 At least one method element is required.
5 The resources element is optional.
6 At least one resource element is required when the resources element is present.
7 The path attribute is required and is always treated as a Java regular expression.
8 The allowed-methods element is optional. Leaving it out is useful when you need to prevent adding additional HTTP methods to a sub-resource as is the case with /v1/status.* in this example.
9 At least one method element is required when the allowed-methods element is present.

Additional Information

CORS Overview

CORS is not a security feature. It is a mechanism for informing clients (e.g., web browsers) of conditions when client-side security may be slightly relaxed in certain circumstances. That is, the security lies completely within the client. Simply leaving out the Origin header in the request completely bypasses the CORS spec (and thus this filter). You should continue securing your API in other ways using proper authentication and authorization mechanisms.

Same-Origin Policy

For security purposes, web browsers follow the Same-Origin policy. If a user were to visit a website containing malicious code, the web browser would prevent the malicious code from trying to send requests to different websites on the user’s behalf. This is especially useful when the user is authenticated on those other websites. However, sometimes a website needs to be able to get data or perform an action on a different website, but how can the client know which websites allow this and under what circumstances? This is where CORS comes in.

CORS

Instead of the web browser immediately dropping any attempt to send a request to a third-party server, it can send the request to that server with CORS headers to see if the server trusts the origin server. The address of the origin server is sent in the Origin header. If the response from the third-party server does not contain the appropriate CORS headers (i.e., the server is not CORS aware) or if the CORS headers indicate the Origin is not allowed to send requests to it, the browser will drop the response (i.e., the client-side code from the origin server will not get to see the contents of the response from the third-party server).

CORS Preflight Requests

Even though the web browser will prevent the client-side code from seeing the response from the third-party server, the request may have still been processed by the third-party server. To mitigate this issue, the web browser will send a CORS Preflight Request to the third-party server to first verify that the Origin and HTTP method are allowed (among a few other things) before sending the CORS Actual Request. If the response to the CORS Preflight Request indicates the CORS Actual Request would not include the appropriate CORS headers, the web browser will not proceed with sending the CORS Actual Request.

Because the CORS Preflight Request is asking if a CORS Actual Request would be allowed and not for the request to actually be processed, this type of request is completely handled by the CORS filter in Repose. No other filters after the CORS filter will process the request, and the request will not reach the origin service.

A web browser may choose to skip sending a CORS Preflight Request if the HTTP method is GET, HEAD, or POST, the request headers do not include anything other than Accept, Accept-Language, and Content-Language, and the request does not require any cookies, HTTP authentication, nor use of any client-side SSL certificates. Otherwise, the web browser must make a CORS Preflight Request. For example, if your origin service requires an X-Auth-Token header, the web browser will always send a CORS Preflight Request before sending the CORS Actual Request.

Origin Header

Some web browsers (e.g., Chrome and Safari) will send the Origin header for same-origin (i.e., non-CORS) requests in addition to CORS requests which is technically allowed under RFC 6454. This is typically not a concern for servers handling CORS because the unnecessary inclusion of CORS headers will be ignored by the client if they are not needed to process the response. Because Repose has the added ability to reject requests with unapproved origins, additional logic is required to differentiate between CORS requests and same-origin requests when the Origin header is present.

Requests are considered same-origin requests when the Origin header matches the original Host header set by the client according to the comparison rules in RFC 6454 Section 5. If the X-Forwarded-Host header is present in the request, the first value will be used as the host. If the header is not present or it cannot be parsed as a URI (when the URI scheme is prepended to it), then the Host header will be used instead. This check is not performed for CORS Preflight Requests since web browsers should not be sending a CORS preflight header for a same-origin request.

If a proxy server sitting between the client and Repose rewrites the Host without updating the X-Forwarded-Host header with the original value, Repose will not be able to correctly identify same-origin requests coming from that client. This may result in requests from that client being incorrectly rejected on the basis that they are CORS requests when they may in fact be same-origin requests.