From a6079a4cb61327efc42016a6490e1edc3be7006b Mon Sep 17 00:00:00 2001 From: Harshil Agrawal Date: Fri, 25 Apr 2025 11:44:43 +0200 Subject: [PATCH 1/7] update Presigned URL vs Binding --- src/content/docs/r2/api/s3/presigned-urls.mdx | 83 ++++++++++++------- 1 file changed, 52 insertions(+), 31 deletions(-) diff --git a/src/content/docs/r2/api/s3/presigned-urls.mdx b/src/content/docs/r2/api/s3/presigned-urls.mdx index caa680db54e3c6a..070f55e3d5520d0 100644 --- a/src/content/docs/r2/api/s3/presigned-urls.mdx +++ b/src/content/docs/r2/api/s3/presigned-urls.mdx @@ -50,16 +50,6 @@ R2 currently supports the following methods when generating a presigned URL: `POST`, which performs uploads via native HTML forms, is not currently supported. -## Generate presigned URLs - -Generate a presigned URL by referring to the following examples: - -- [AWS SDK for Go](/r2/examples/aws/aws-sdk-go/#generate-presigned-urls) -- [AWS SDK for JS v3](/r2/examples/aws/aws-sdk-js-v3/#generate-presigned-urls) -- [AWS SDK for JS](/r2/examples/aws/aws-sdk-js/#generate-presigned-urls) -- [AWS SDK for PHP](/r2/examples/aws/aws-sdk-php/#generate-presigned-urls) -- [AWS CLI](/r2/examples/aws/aws-cli/#generate-presigned-urls) - ## Presigned URL alternative with Workers A valid alternative design to presigned URLs is to use a Worker with a [binding](/workers/runtime-apis/bindings/) that implements your security policy. @@ -72,14 +62,33 @@ A binding is defined in the Wrangler file of your Worker project's directory. ::: -A possible use case may be restricting an application to only be able to upload to a specific URL. With presigned URLs, your central signing application might look like the following JavaScript code running on Cloudflare Workers, workerd, or another platform. +Refer to [Use R2 from Workers](/r2/api/workers/workers-api-usage/) to learn how to bind a bucket to a Worker and use the binding to interact with your bucket. + +## Generate presigned URLs + +Generate a presigned URL by referring to the following examples: + +- [AWS SDK for Go](/r2/examples/aws/aws-sdk-go/#generate-presigned-urls) +- [AWS SDK for JS v3](/r2/examples/aws/aws-sdk-js-v3/#generate-presigned-urls) +- [AWS SDK for JS](/r2/examples/aws/aws-sdk-js/#generate-presigned-urls) +- [AWS SDK for PHP](/r2/examples/aws/aws-sdk-php/#generate-presigned-urls) +- [AWS CLI](/r2/examples/aws/aws-cli/#generate-presigned-urls) + +### Example of generating presigned URLs + +A possible use case may be restricting an application to only be able to upload to a specific URL. With presigned URLs, your central signing application might look like the following JavaScript code running on Cloudflare Workers, workerd, or another platform (you might have to update the code based on the platform you are using). -If the Worker received a request for `https://example.com/uploads/dog.png`, it would respond with a presigned URL allowing a user to upload to your R2 bucket at the `/uploads/dog.png` path. +If the application received a request for `https://example.com/uploads/dog.png`, it would respond with a presigned URL allowing a user to upload to your R2 bucket at the `/uploads/dog.png` path. + +To create a presigned URL, you will need to either use a package that implements the signing algorithm or implement the signing algorithm yourself. In this example, the `aws4fetch` package is used. You also need to have an access key ID and secret access key. Refer to [R2 API tokens](/r2/api/tokens/) for more information. ```ts import { AwsClient } from "aws4fetch"; -const r2 = new AwsClient({ +// Create a new client +// Replace with your own access key ID and secret access key +// Make sure to store these securely and not expose them +const client = new AwsClient({ accessKeyId: "", secretAccessKey: "", }); @@ -98,6 +107,7 @@ export default { return new Response("Missing a filepath", { status: 400 }); } + // Replace with your bucket name and account ID const bucketName = ""; const accountId = ""; @@ -111,7 +121,7 @@ export default { // Specify a custom expiry for the presigned URL, in seconds url.searchParams.set("X-Amz-Expires", "3600"); - const signed = await r2.sign( + const signed = await client.sign( new Request(url, { method: "PUT", }), @@ -128,12 +138,16 @@ export default { } satisfies ExportedHandler; ``` -Notice the total absence of any configuration or token secrets present in the Worker code. Instead, in your [Wrangler configuration file](/workers/wrangler/configuration/), you would create a [binding](/r2/api/workers/workers-api-usage/#3-bind-your-bucket-to-a-worker) to whatever bucket represents the bucket you will upload to. Additionally, authorization is handled in-line with the upload which can reduce latency. +## Differences between presigned URLs and R2 binding + +When using the R2 binding, you will not need any token secrets in your Worker code. Instead, in your [Wrangler configuration file](/workers/wrangler/configuration/), you will create a [binding](/r2/api/workers/workers-api-usage/#3-bind-your-bucket-to-a-worker) to your R2 bucket. Additionally, authorization is handled in-line which can reduce latency. -In some cases, Workers lets you implement certain functionality more easily. For example, if you wanted to offer a write-once guarantee so that users can only upload to a path once, with pre-signed URLs, you would need to sign specific headers and require the sender to send them. You can modify the previous Worker to sign additional headers: +However, when using presigned URLs, you will need to create and use the token secrets in your Worker code. + +In some cases, Workers lets you implement certain functionality more easily. For example, if you wanted to offer a write-once guarantee so that users can only upload to a path once, with pre-signed URLs, you would need to sign specific headers and require the sender to send the same headers. You can modify the previous example to sign additional headers: ```ts -const signed = await r2.sign( +const signed = await client.sign( new Request(url, { method: "PUT", }), @@ -146,22 +160,29 @@ const signed = await r2.sign( ); ``` -Note that the caller has to add the same `If-Unmodified-Since` header to use the URL. The caller cannot omit the header or use a different header. If the caller uses a different header, the presigned URL signature would not match, and they would receive a `403/SignatureDoesNotMatch`. +```ts +// Use the presigned URL to upload the file +const response = await fetch(signed.url, { + method: "PUT", + body: file, + headers: { + "If-Unmodified-Since": "Tue, 28 Sep 2021 16:00:00 GMT", + }, +}); +``` + +Note that the caller has to add the same `If-Unmodified-Since` header to use the URL. The caller cannot omit the header or use a different header since the signature covers the headers. If the caller uses a different header, the presigned URL signature would not match, and they would receive a `403/SignatureDoesNotMatch`. -In a Worker, you would change your upload to: +If you are using R2 bindings, you would change your upload to: ```ts -const existingObject = await env.DROP_BOX_BUCKET.put( - url.toString().substring(1), - request.body, - { - onlyIf: { - // No objects will have been uploaded before September 28th, 2021 which - // is the initial R2 announcement. - uploadedBefore: new Date(1632844800000), - }, +const existingObject = await env.R2_BUCKET.put(key, request.body, { + onlyIf: { + // No objects will have been uploaded before September 28th, 2021 which + // is the initial R2 announcement. + uploadedBefore: new Date(1632844800000), }, -); +}); if (existingObject?.etag !== request.headers.get("etag")) { return new Response("attempt to overwrite object", { status: 400 }); } @@ -169,11 +190,11 @@ if (existingObject?.etag !== request.headers.get("etag")) { Cloudflare Workers currently have some limitations that you may need to consider: -- You cannot upload more than 100 MiB (200 MiB for Business customers) to a Worker. +- You cannot upload more than 100 MiB (200 MiB for Business customers) with a Worker. - Enterprise customers can upload 500 MiB by default and can ask their account team to raise this limit. - Detecting [precondition failures](/r2/api/s3/extensions/#conditional-operations-in-putobject) is currently easier with presigned URLs as compared with R2 bindings. -Note that these limitations depends on R2's extension for conditional uploads. Amazon's S3 service does not offer such functionality at this time. +Note that these limitations depend on R2's extension for conditional uploads. Amazon's S3 service does not offer such functionality at this time. ## Differences between presigned URLs and public buckets From ce165c2407fc150fe9b10ed0e2806d9ddfa4e082 Mon Sep 17 00:00:00 2001 From: Jun Lee Date: Fri, 25 Apr 2025 11:17:36 +0100 Subject: [PATCH 2/7] Apply suggestions from code review --- src/content/docs/r2/api/s3/presigned-urls.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/content/docs/r2/api/s3/presigned-urls.mdx b/src/content/docs/r2/api/s3/presigned-urls.mdx index 070f55e3d5520d0..d0c556725728ea3 100644 --- a/src/content/docs/r2/api/s3/presigned-urls.mdx +++ b/src/content/docs/r2/api/s3/presigned-urls.mdx @@ -80,7 +80,7 @@ A possible use case may be restricting an application to only be able to upload If the application received a request for `https://example.com/uploads/dog.png`, it would respond with a presigned URL allowing a user to upload to your R2 bucket at the `/uploads/dog.png` path. -To create a presigned URL, you will need to either use a package that implements the signing algorithm or implement the signing algorithm yourself. In this example, the `aws4fetch` package is used. You also need to have an access key ID and secret access key. Refer to [R2 API tokens](/r2/api/tokens/) for more information. +To create a presigned URL, you will need to either use a package that implements the signing algorithm, or implement the signing algorithm yourself. In this example, the `aws4fetch` package is used. You also need to have an access key ID and a secret access key. Refer to [R2 API tokens](/r2/api/tokens/) for more information. ```ts import { AwsClient } from "aws4fetch"; @@ -171,7 +171,7 @@ const response = await fetch(signed.url, { }); ``` -Note that the caller has to add the same `If-Unmodified-Since` header to use the URL. The caller cannot omit the header or use a different header since the signature covers the headers. If the caller uses a different header, the presigned URL signature would not match, and they would receive a `403/SignatureDoesNotMatch`. +Note that the caller has to add the same `If-Unmodified-Since` header to use the URL. The caller cannot omit the header or use a different header, since the signature covers the headers. If the caller uses a different header, the presigned URL signature would not match, and they would receive a `403/SignatureDoesNotMatch`. If you are using R2 bindings, you would change your upload to: From 25ac87622c3a9011f0d9d765a851c9be279c9313 Mon Sep 17 00:00:00 2001 From: Harshil Agrawal Date: Fri, 25 Apr 2025 13:26:59 +0200 Subject: [PATCH 3/7] update limitations --- src/content/docs/r2/api/s3/presigned-urls.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/content/docs/r2/api/s3/presigned-urls.mdx b/src/content/docs/r2/api/s3/presigned-urls.mdx index 070f55e3d5520d0..1321970d6b9e594 100644 --- a/src/content/docs/r2/api/s3/presigned-urls.mdx +++ b/src/content/docs/r2/api/s3/presigned-urls.mdx @@ -188,9 +188,9 @@ if (existingObject?.etag !== request.headers.get("etag")) { } ``` -Cloudflare Workers currently have some limitations that you may need to consider: +When using the bindings, you may need to consider the following limitations: -- You cannot upload more than 100 MiB (200 MiB for Business customers) with a Worker. +- You cannot upload more than 100 MiB (200 MiB for Business customers) when using the bindings. - Enterprise customers can upload 500 MiB by default and can ask their account team to raise this limit. - Detecting [precondition failures](/r2/api/s3/extensions/#conditional-operations-in-putobject) is currently easier with presigned URLs as compared with R2 bindings. From 6ba1abe3fd856a4839f4ff4cd39f5dd4935e1aa8 Mon Sep 17 00:00:00 2001 From: Jun Lee Date: Fri, 25 Apr 2025 13:32:40 +0100 Subject: [PATCH 4/7] Apply suggestions from code review --- src/content/docs/r2/api/s3/presigned-urls.mdx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/content/docs/r2/api/s3/presigned-urls.mdx b/src/content/docs/r2/api/s3/presigned-urls.mdx index fb22bd410f1f037..3ed659902ab620e 100644 --- a/src/content/docs/r2/api/s3/presigned-urls.mdx +++ b/src/content/docs/r2/api/s3/presigned-urls.mdx @@ -76,7 +76,7 @@ Generate a presigned URL by referring to the following examples: ### Example of generating presigned URLs -A possible use case may be restricting an application to only be able to upload to a specific URL. With presigned URLs, your central signing application might look like the following JavaScript code running on Cloudflare Workers, workerd, or another platform (you might have to update the code based on the platform you are using). +A possible use case may be restricting an application to only be able to upload to a specific URL. With presigned URLs, your central signing application might look like the following JavaScript code running on Cloudflare Workers, `workerd`, or another platform (you might have to update the code based on the platform you are using). If the application received a request for `https://example.com/uploads/dog.png`, it would respond with a presigned URL allowing a user to upload to your R2 bucket at the `/uploads/dog.png` path. @@ -140,11 +140,16 @@ export default { ## Differences between presigned URLs and R2 binding -When using the R2 binding, you will not need any token secrets in your Worker code. Instead, in your [Wrangler configuration file](/workers/wrangler/configuration/), you will create a [binding](/r2/api/workers/workers-api-usage/#3-bind-your-bucket-to-a-worker) to your R2 bucket. Additionally, authorization is handled in-line which can reduce latency. +- When using an R2 binding, you will not need any token secrets in your Worker code. Instead, in your [Wrangler configuration file](/workers/wrangler/configuration/), you will create a [binding](/r2/api/workers/workers-api-usage/#3-bind-your-bucket-to-a-worker) to your R2 bucket. Additionally, authorization is handled in-line, which can reduce latency. -However, when using presigned URLs, you will need to create and use the token secrets in your Worker code. +- When using presigned URLs, you will need to create and use the token secrets in your Worker code. -In some cases, Workers lets you implement certain functionality more easily. For example, if you wanted to offer a write-once guarantee so that users can only upload to a path once, with pre-signed URLs, you would need to sign specific headers and require the sender to send the same headers. You can modify the previous example to sign additional headers: +In some cases, R2 bindings let you implement certain functionality more easily. For example, if you wanted to offer a write-once guarantee so that users can only upload to a path once, with pre-signed URLs: + + - With R2 binding: You only need to pass the parameter once. + - With presigned URLs: You need to sign specific headers and require the sender to send the same headers. + + You can modify the previous example to sign additional headers: ```ts const signed = await client.sign( From 8e38ec0a4ec896710a75dfd10e7ca0047d4a98bc Mon Sep 17 00:00:00 2001 From: Jun Lee Date: Fri, 25 Apr 2025 13:41:23 +0100 Subject: [PATCH 5/7] Cleaning up structure of the doc with tabs. --- src/content/docs/r2/api/s3/presigned-urls.mdx | 62 +++++++++++-------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/src/content/docs/r2/api/s3/presigned-urls.mdx b/src/content/docs/r2/api/s3/presigned-urls.mdx index 3ed659902ab620e..579f2fa14316662 100644 --- a/src/content/docs/r2/api/s3/presigned-urls.mdx +++ b/src/content/docs/r2/api/s3/presigned-urls.mdx @@ -3,6 +3,8 @@ title: Presigned URLs pcx_content_type: concept --- +import {Tabs, TabItem } from "~/components"; + Presigned URLs are an [S3 concept](https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html) for sharing direct access to your bucket without revealing your token secret. A presigned URL authorizes anyone with the URL to perform an action to the S3 compatibility endpoint for an R2 bucket. By default, the S3 endpoint requires an `AUTHORIZATION` header signed by your token. Every presigned URL has S3 parameters and search parameters containing the signature information that would be present in an `AUTHORIZATION` header. The performable action is restricted to a specific resource, an [operation](/r2/api/s3/api/), and has an associated timeout. There are three kinds of resources in R2: @@ -141,15 +143,41 @@ export default { ## Differences between presigned URLs and R2 binding - When using an R2 binding, you will not need any token secrets in your Worker code. Instead, in your [Wrangler configuration file](/workers/wrangler/configuration/), you will create a [binding](/r2/api/workers/workers-api-usage/#3-bind-your-bucket-to-a-worker) to your R2 bucket. Additionally, authorization is handled in-line, which can reduce latency. - - When using presigned URLs, you will need to create and use the token secrets in your Worker code. In some cases, R2 bindings let you implement certain functionality more easily. For example, if you wanted to offer a write-once guarantee so that users can only upload to a path once, with pre-signed URLs: - - With R2 binding: You only need to pass the parameter once. - - With presigned URLs: You need to sign specific headers and require the sender to send the same headers. - - You can modify the previous example to sign additional headers: +- With R2 binding: You only need to pass the parameter once. +- With presigned URLs: You need to sign specific headers and require the sender to send the same headers. + + + + +If you are using R2 bindings, you would change your upload to: + +```ts +const existingObject = await env.R2_BUCKET.put(key, request.body, { + onlyIf: { + // No objects will have been uploaded before September 28th, 2021 which + // is the initial R2 announcement. + uploadedBefore: new Date(1632844800000), + }, +}); +if (existingObject?.etag !== request.headers.get("etag")) { + return new Response("attempt to overwrite object", { status: 400 }); +} +``` + +When using R2 bindings, you may need to consider the following limitations: + +- You cannot upload more than 100 MiB (200 MiB for Business customers) when using R2 bindings. +- Enterprise customers can upload 500 MiB by default and can ask their account team to raise this limit. +- Detecting [precondition failures](/r2/api/s3/extensions/#conditional-operations-in-putobject) is currently easier with presigned URLs as compared with R2 bindings. + +Note that these limitations depend on R2's extension for conditional uploads. Amazon's S3 service does not offer such functionality at this time. + + +You can modify the previous example to sign additional headers: ```ts const signed = await client.sign( @@ -178,28 +206,8 @@ const response = await fetch(signed.url, { Note that the caller has to add the same `If-Unmodified-Since` header to use the URL. The caller cannot omit the header or use a different header, since the signature covers the headers. If the caller uses a different header, the presigned URL signature would not match, and they would receive a `403/SignatureDoesNotMatch`. -If you are using R2 bindings, you would change your upload to: - -```ts -const existingObject = await env.R2_BUCKET.put(key, request.body, { - onlyIf: { - // No objects will have been uploaded before September 28th, 2021 which - // is the initial R2 announcement. - uploadedBefore: new Date(1632844800000), - }, -}); -if (existingObject?.etag !== request.headers.get("etag")) { - return new Response("attempt to overwrite object", { status: 400 }); -} -``` - -When using the bindings, you may need to consider the following limitations: - -- You cannot upload more than 100 MiB (200 MiB for Business customers) when using the bindings. -- Enterprise customers can upload 500 MiB by default and can ask their account team to raise this limit. -- Detecting [precondition failures](/r2/api/s3/extensions/#conditional-operations-in-putobject) is currently easier with presigned URLs as compared with R2 bindings. - -Note that these limitations depend on R2's extension for conditional uploads. Amazon's S3 service does not offer such functionality at this time. + + ## Differences between presigned URLs and public buckets From 5f1b2e79d444573089896ed859a807b4b8986c7f Mon Sep 17 00:00:00 2001 From: Jun Lee Date: Fri, 25 Apr 2025 13:42:51 +0100 Subject: [PATCH 6/7] Small fix --- src/content/docs/r2/api/s3/presigned-urls.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/r2/api/s3/presigned-urls.mdx b/src/content/docs/r2/api/s3/presigned-urls.mdx index 579f2fa14316662..c6032dac694b88c 100644 --- a/src/content/docs/r2/api/s3/presigned-urls.mdx +++ b/src/content/docs/r2/api/s3/presigned-urls.mdx @@ -145,7 +145,7 @@ export default { - When using an R2 binding, you will not need any token secrets in your Worker code. Instead, in your [Wrangler configuration file](/workers/wrangler/configuration/), you will create a [binding](/r2/api/workers/workers-api-usage/#3-bind-your-bucket-to-a-worker) to your R2 bucket. Additionally, authorization is handled in-line, which can reduce latency. - When using presigned URLs, you will need to create and use the token secrets in your Worker code. -In some cases, R2 bindings let you implement certain functionality more easily. For example, if you wanted to offer a write-once guarantee so that users can only upload to a path once, with pre-signed URLs: +In some cases, R2 bindings let you implement certain functionality more easily. For example, if you wanted to offer a write-once guarantee so that users can only upload to a path once: - With R2 binding: You only need to pass the parameter once. - With presigned URLs: You need to sign specific headers and require the sender to send the same headers. From f57dc42d3f693e1992f564fe6f3efd8d9651775f Mon Sep 17 00:00:00 2001 From: Jun Lee Date: Fri, 25 Apr 2025 14:05:34 +0100 Subject: [PATCH 7/7] Further language clarification --- src/content/docs/r2/api/s3/presigned-urls.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/content/docs/r2/api/s3/presigned-urls.mdx b/src/content/docs/r2/api/s3/presigned-urls.mdx index c6032dac694b88c..de198f7095879e2 100644 --- a/src/content/docs/r2/api/s3/presigned-urls.mdx +++ b/src/content/docs/r2/api/s3/presigned-urls.mdx @@ -147,8 +147,8 @@ export default { In some cases, R2 bindings let you implement certain functionality more easily. For example, if you wanted to offer a write-once guarantee so that users can only upload to a path once: -- With R2 binding: You only need to pass the parameter once. -- With presigned URLs: You need to sign specific headers and require the sender to send the same headers. +- With R2 binding: You only need to pass the header once. +- With presigned URLs: You need to first sign specific headers, then request the user to send the same headers.