Configure CORS rules
Let web applications on other origins call a bucket from the browser by adding CORS rules through the REST API, and check whether they're in force.
Add CORS rules to a bucket so that scripts on web pages from other origins can send requests to it. You add and remove rules through the REST API; the console shows a bucket's rules and whether they're in force.
Before you begin
- You need the Owner or Contributor role, or a custom role with Manage access to Block & Object Storage, on the subscription or on the project.
- You need an API token with the
storage:writescope, in the environment variablePC_TOKEN. See Service accounts and API tokens and Authenticate with API tokens. - You need the bucket's ID: on the bucket's Overview tab, copy Bucket ID.
- You need your project's ID. See Find a project's ID.
How CORS rules apply
The object store keeps one CORS policy per bucket, so the platform merges all of a bucket's rules into that policy:
- Allowed origins are the origins of all the rules together.
- Exposed headers, the response headers that browser scripts can read, are the headers of all the rules together.
- Max age, how long a browser may cache a preflight response, is the longest value of all the rules.
A bucket without CORS rules has no CORS policy of its own.
Rule fields
| Field | Rules |
|---|---|
origins | At least one. Each origin is *, for any origin, or an exact scheme://host with an optional :port, such as https://app.example. Use ASCII only, and write internationalized domain names in punycode. No path, no wildcard in the host, no user name or password, and no spaces. Origins are stored in lowercase, and the object store compares them exactly. |
methods | At least one, such as GET or PUT. |
headers | The response headers that browser scripts can read, such as ETag. Each entry is one header name, or *. Can be empty. |
maxAgeSeconds | 0 or more. How long, in seconds, a browser may cache a preflight response. |
A bucket can have up to 100 CORS rules. The merged list of origins, and the merged list of exposed headers, must each fit within the object store's limit for one metadata value; a rule that would exceed it is refused, and the error states the limit in bytes.
Add a CORS rule
Send the rule to the bucket's configuration rules:
curl -X POST "https://<api-host>/api/v1/scopes/<project-id>/storage/buckets/<bucket-id>/configuration-rules" \
-H "Authorization: Bearer $PC_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"type":"cors","cors":{"origins":["https://app.example"],"methods":["GET","PUT"],"headers":["ETag"],"maxAgeSeconds":3600}}'<api-host>: the host name of the API. See URL structure.<project-id>: the ID of the project that holds the bucket.<bucket-id>: the bucket's ID.
Check the response. A successful request returns 201 Created with the new rule, including its id:
{"id":"<rule-id>","bucketId":"<bucket-id>","type":"cors","cors":{"id":"<rule-id>","origins":["https://app.example"],"methods":["GET","PUT"],"headers":["ETag"],"maxAgeSeconds":3600},"lifecycle":null,"replication":null}Send an Idempotency-Key header with a new value for each rule, as in the example. If you repeat a request with the same key and body, the API returns the rule that the first request created instead of adding a second one. Without the header, a repeated request adds a second rule. For the request format, see POST/scopes/{scopeId}/storage/buckets/{bucketId}/configuration-rules.
The rule is saved at once and applied to the object store in the background. To see when it's in force, check the bucket's CORS tab.
Check whether the rules are in force
In the console, go to StorageObject Storage, and select the bucket's name.
Select the CORS tab.
The tab lists each rule's Allowed origins, Exposed headers, and Max age. Above the list, a line shows the state of the latest change:
| State | What it means |
|---|---|
| In force on the object store. | The object store holds the bucket's current rules. |
| Being applied to the object store… | The change is being applied. The platform retries when the object store is briefly unavailable. |
| Not applied — the object store keeps its previous CORS policy: <reason> | The object store refused the change. It keeps its previous CORS policy. |
Through the API, GET/scopes/{scopeId}/storage/buckets/{bucketId} returns the rules in corsRules and the state in corsApply.status: pending, applied, or failed. corsApply is null when the bucket's rules have never changed.
Remove a CORS rule
Find the rule's id in corsRules:
curl "https://<api-host>/api/v1/scopes/<project-id>/storage/buckets/<bucket-id>" \
-H "Authorization: Bearer $PC_TOKEN"Delete the rule:
curl -X DELETE "https://<api-host>/api/v1/scopes/<project-id>/storage/buckets/<bucket-id>/configuration-rules/<rule-id>" \
-H "Authorization: Bearer $PC_TOKEN"<rule-id>: the rule's id.
A successful request returns 204 No Content, and the platform applies the remaining rules to the object store. When you remove the last rule, the bucket's CORS policy is removed. See DELETE/scopes/{scopeId}/storage/buckets/{bucketId}/configuration-rules/{configurationRuleId}.
To change a rule, remove it and add a new one. Rules can't be edited.
Result
Browser requests from the allowed origins receive the CORS headers that the bucket's merged policy sets.
Troubleshooting
| Response | Cause | Resolution |
|---|---|---|
422 with code VALIDATION_FAILED and field cors.origins | An origin isn't * or an exact ASCII scheme://host[:port]. | Remove the path or the wildcard, and write internationalized names in punycode. |
422 with field cors.headers | A header entry isn't a single header name or *. | Send one header name per entry. |
422 with details.reason cors_policy_too_large | The merged origins or headers would exceed the object store's limit, given in details.limitBytes. | Remove origins, headers, or rules first. |
409 with code QUOTA_EXCEEDED | The bucket already has 100 CORS rules. | Remove rules, or combine their origins into fewer rules. |
409 with code CONFLICT | The bucket is being created or deleted, or it's in an error state. | If the bucket is being created, wait until its creation finishes. A bucket that's being deleted or is in an error state can't take new rules. |
422 with details.reason lifecycle_not_supported or replication_not_supported | The rule's type is lifecycle or replication. The platform doesn't apply these rule types. | Send CORS rules only. |
| Not applied — the object store keeps its previous CORS policy: <reason> on the CORS tab | The object store refused the merged policy. | Remove the rule you added last. Every change applies the bucket's whole rule set again. |