Hide
Google Cloud Storage

Cross-Origin Resource Sharing (CORS)

The same-origin policy is a security policy enforced on client-side web apps (e.g., web browsers) to prevent interactions between resources from different origins. While useful for preventing malicious behavior, this security measure also prevents useful and legitimate interactions between known origins. For example, a script on a page hosted from Google App Engine at example.appspot.com might want to use static resources stored in a Google Cloud Storage bucket at example.storage.googleapis.com. However, because these are two different origins from the perspective of the browser, the browser won't allow a script from example.appspot.com to fetch resources from example.storage.googleapis.com using XMLHttpRequest because the resource being fetched is from a different origin.

The Cross Origin Resource Sharing (CORS) spec was developed by the World Wide Web Consortium (W3C) to get around this limitation. Google Cloud Storage supports this specification by allowing you to configure your buckets to return CORS-compliant responses. Continuing the above example, because Google Cloud Storage supports CORS, a browser can ask example.storage.googleapis.com for permission to share its resources with scripts from example.appspot.com.

Contents

  1. How CORS Works
  2. Configuring CORS on a Bucket
  3. Sending a Cross-Domain Request to Google Cloud Storage
  4. Troubleshooting CORS-Related Problems

How CORS Works

On the client-side, when the web client (browser) makes a request to Google Cloud Storage, it automatically adds the Origin header containing the origin of the resource seeking to share a cross domain's resources, for example, Origin:http://www.example.appspot.com. Google Cloud Storage looks up the origin in the request header in its own CORS configuration to determine whether the incoming origin is allowed or not, and whether the incoming request method is allowed for that origin. If the origin and method are allowed, Google Cloud Storage includes the header Access-Control-Allow-Origin in its response. The client (e.g., browser) checks this response header to verify that the domain in the response matches the domain specified in original request, and if these match, the request proceeds. If there is not a match, or if the Access-Control-Allow-Origin header is not present in the response, the request is disallowed.

Supporting CORS on the Client

Most clients (such as browsers) use the XMLHttpRequest object to make a cross-domain request. XMLHttpRequest takes care of all the work of inserting the right headers and handling the CORS interaction with the server. This means you don't add any new code to take advantage of CORS support, it will simply work as expected for Google Cloud Storage buckets configured for CORS.

Configuring CORS on a Bucket

Google Cloud Storage allows you to set CORS configuration at the bucket-level only. If you want to make a bucket available for cross-domain resource sharing, you set a CORS configuration on the bucket that contains all the origins you wish to share the bucket with, and the request methods that you want to allow on that bucket.

There are several ways to set CORS configuration on a bucket:

  • Use the gsutil tool. For example, use the gsutil cors set command to set or modify cors configuration. (Optionally, use gsutil cors get to list a bucket's cors configuration.)
  • Send a request directly to the XML API or the JSON API. For example, you can use the PUT Bucket method in the XML API, with the ?cors subresource to set or modify cors configuration. (Optionally, use GET Bucket with the ?cors subresource to list a bucket's cors configuration.
  • Use one of the client libraries for Google Cloud Storage.

Whichever method you use to set CORS, you have to supply the CORS configuration data with the request, which specifies all the origins and request methods that are allowed to access the buckets. For example, to allow scripts at example.appspot.com to use static resources stored in the bucket at example.storage.googleapis.com, you can use the following gsutil command or XML API request:

gsutil

gsutil cors set cors-json-file.json gs://example

Where cors-json-file.json contains:

[
    {
      "origin": ["http://example.appspot.com"],
      "responseHeader": ["Content-Type"],
      "method": ["GET", "HEAD", "DELETE"],
      "maxAgeSeconds": 3600
    }
]

XML API

PUT http://storage.googleapis.com/example?cors HTTP/1.1
Host: storage.googleapis.com
Content-Length: 412
Authorization: Bearer  ya29.1.AADtN_WObQo0sp50vPJRiuscdtHmpSjOCLhk_4E9rUPsI766udLdOpJkbPZQ4gxBHfPjEvDLuUE

<?xml version="1.0" encoding="UTF-8"?>
<CorsConfig>
  <Cors>
    <Origins>
      <Origin>http://example.appspot.com</Origin>
    </Origins>
    <Methods>
      <Method>GET</Method>
      <Method>HEAD</Method>
      <Method>DELETE</Method>
    </Methods>
    <ResponseHeaders>
      <ResponseHeader>Content-Type</ResponseHeader>
    </ResponseHeaders>
    <MaxAgeSec>3600</MaxAgeSec>
  </Cors>
</CorsConfig>

The fields for the CORS configuration XML are described in detail in the PUT Bucket method documentation.

Sending a Cross-Domain Request to Google Cloud Storage

You can use any allowed XML API URI format to obtain a response from Google Cloud Storage that contains the CORS headers. For information about XML API URI formats, see Request URIs.

Back to top

If you run into unexpected behavior when accessing buckets using CORS, try the following steps:

  1. Use gsutil cors get on the problem bucket to ensure the bucket has the expected CORS configuration.
  2. Capture a full request-response using a tool of your choice. In a Chrome browser you can use the standard developer tools to see this:
    1. Click the Chrome menu Chrome menu icon. on the browser toolbar.
    2. Select More Tools > Developer Tools.
    3. Click the Network tab.
    4. Send your browser request and in the pane displaying the network activity, locate the request you're interested in.
    5. In the Name column, click the name corresponding to the request.
    6. Click the Headers tab to see the response headers, or the Response tab to see the content of the response.
  3. Ensure that the request actually has an Origin header.
  4. Ensure that the Origin header you're sending matches at least one of the Origins in the CORS configuration you retrieved in Step 1. Note that the scheme, host, and port must all match. For example, http://origin.example.com does not match https://origin.example.com, nor does it match http://origin.example.com:80 or http://origin.example.com:5151.
  5. Ensure that the Method you're sending (or the method specified in Access-Control-Request-Method, if this a preflight request) is a match for one of the methods in your CORS configuration, that also is a match for Origin. To be applied, a CORS configuration entry must match on both Origin and Method. If you have two CORS configuration entries, one of which matches on Origin but not Method, and the other matches on Method but not Origin, neither one will be used, and no CORS headers will be included in the response.
  6. If this is a preflight request, check if the preflight request includes one or more Access-Control-Request-Header. If so, then ensure that the matching CORS configuration entry includes a <ResponseHeader> entry for each requested header. All headers named in the Access-Control-Request-Header must be in the CORS configuration for the preflight request to succeed and include CORS headers in the response.
  7. If you are trying to reproduce the problem, and you're not seeing a request/response, it is possible that your browser has cached an earlier failed preflight request attempt. Clearing your browser's cache may also clear the preflight cache.

    The default setting for <MaxAgeSec> is 1800 seconds (30 minutes). If you think clearing your browsers cache in the usual way is not also clearing the CORS preflight cache, you should set <MaxAgeSec> on your CORS configuration to a lower value, wait 30 minutes, then try again. This should perform a new preflight request, which will fetch the new CORS configuration and also set <MaxAgeSec> to the new lower value you selected, allowing the cache entries to be purged more frequently. Once you have debugged your problem, you should raise <MaxAgeSec> back to a higher value, to reduce the preflight traffic to your bucket.

  8. When using the resumable upload protocol, the Origin from the first (start upload) request is always used to decide the Access-Control-Allow-Origin header in the response, even if you use a different Origin for subsequent request. Therefore, you should either use the same origin for the first and subsequent requests, or if the first request has a different origin than subsequent requests, use the XML API with the CORS configuration set to <Origin>*</Origin>.
  9. Back to top