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
- How CORS Works
- Configuring CORS on a Bucket
- Sending a Cross-Domain Request to Google Cloud Storage
- 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 thegsutil cors set
command to set or modify cors configuration. (Optionally, usegsutil 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": ["x-meta-goog-custom"], "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>x-goog-meta-custom</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 URI format to obtain a response from Google Cloud Storage that contains the CORS headers. For information about URI formats, see Request URIs
Troubleshooting CORS-Related Problems
If you run into unexpected behavior when accessing buckets using CORS, try the following steps:
-
Use
gsutil cors get
on the problem bucket to ensure the bucket has the expected CORS configuration. -
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:
- In the Chrome upper right, click the wrench icon.
- Select Tools > Developer Tools
- Click the Network tab.
- Send your browser request and in the pane displaying the network activity, locate the request you're interested in.
- In the Name column, click the name corresponding to the request.
- Click the Headers tab to see the response headers, or the Response tab to see the content of the response.
- Ensure that the request actually has an Origin header
-
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. i.e.
http://origin.example.com
does not matchhttps://origin.example.com
, nor does it matchhttp://origin.example.com:80
orhttp://origin.example.com:5151
. -
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. -
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 theAccess-Control-Request-Header
must be in the CORS configuration for the preflight request to succeed and include CORS headers in the response. -
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.