
Cross-Origin Resource Sharing, or CORS, is one of those Cloud Storage features that looks niche until a Professional Cloud Architect exam question puts it directly in front of you. The setup is almost always the same shape. A web app sits on one origin, a bucket sits on another, and the browser needs to talk to the bucket without your backend acting as a middleman. CORS is the policy layer that decides whether the browser is allowed to make that call.
I want to walk through how it actually works, then ground it in the photo-sharing scenario that tends to show up on the exam.
A browser running JavaScript on https://yourapp.com cannot, by default, send a request to https://storage.googleapis.com and read the response. That is the same-origin policy doing its job. CORS is the mechanism that lets you punch a controlled hole through it. You attach a CORS configuration to your Cloud Storage bucket that lists which origins are allowed, which HTTP methods they can use, and which response headers the browser is permitted to read.
When the browser fires a cross-origin request at the bucket, Cloud Storage checks the request origin against the bucket's CORS configuration. If the origin matches, the response comes back with the appropriate Access-Control-Allow-Origin header and the browser hands the data to the app. If the origin is not on the list, the browser blocks the response and the upload or download fails.
The point of doing this on the bucket, rather than routing everything through your own server, is that the client talks to Cloud Storage directly. You skip the bandwidth, the latency, and the compute cost of streaming bytes through App Engine or Cloud Run just to put them in a bucket.
CORS by itself only controls which origins can talk to the bucket. It does not, on its own, control who is allowed to upload. That is where signed URLs come in, and the combination is what the exam usually tests.
A signed URL is a pre-authorized link your backend generates using a service account's credentials. It carries the permission to perform a specific action on a specific object for a specific window of time. When you pair signed URLs with CORS, you get two layers. CORS says "this origin is allowed to make a cross-origin call to this bucket." The signed URL says "this specific request is authorized to write this specific object until this expiration time." Either layer alone is weaker than the two together.
Here is the canonical scenario. You have a photo-sharing app. The frontend runs on App Engine and is served from yourapp.com. Users upload images straight from their browsers into a Cloud Storage bucket. You do not want the upload bytes to traverse App Engine, because that is wasteful and slow.
The flow looks like this. The user picks a file in the browser. The browser asks the App Engine backend for a signed URL for that upload. App Engine generates the URL using a service account that has write access to the bucket and returns it to the browser. The browser then PUTs the file directly to the signed URL. Cloud Storage checks two things on the way in. First, it validates the signature on the URL, which proves the upload was authorized by your backend. Second, it validates the request origin against the bucket's CORS configuration, which proves the request is coming from a domain you trust. Both checks pass, the object lands in the bucket, and App Engine never touched the bytes.
That is the whole architecture, and it is worth memorizing for the Professional Cloud Architect exam because the question often phrases it as "users upload images directly from their browsers" and asks you to identify the right combination of components.
The CORS configuration is a JSON document that lists allowed origins, methods, response headers, and a max-age for the preflight cache. A minimal version for the photo-sharing case looks like this.
[
{
"origin": ["https://yourapp.com"],
"method": ["GET", "PUT", "POST"],
"responseHeader": ["Content-Type"],
"maxAgeSeconds": 3600
}
]
You apply it to the bucket with gsutil.
gsutil cors set cors-config.json gs://your-bucket-name
That command is the one I would expect to recognize on the exam. The newer gcloud storage equivalent is fine in practice, but gsutil cors set is what the official material has historically used and is the form that shows up in question stems.
If you remember three things about CORS for the Professional Cloud Architect exam, make them these. CORS is configured on the bucket and decides which origins the browser is allowed to talk to. Signed URLs are the authorization layer that decides which specific operations are allowed, and they are typically generated by a backend service like App Engine. Direct browser-to-bucket uploads are the architectural win, because they keep upload bytes off your application servers entirely.
My Professional Cloud Architect course covers CORS and signed URLs for Cloud Storage alongside the rest of the storage and analytics material.