AWS SigV4 in the Browser for Cloudflare R2
Last updated:
Cloudflare R2 speaks an S3-compatible API. In MV3 service workers, you can sign requests on-device using WebCrypto
and send them directly to https://{account}.r2.cloudflarestorage.com
. This post covers canonical requests, the
string-to-sign, and HMAC signatures in the browser.
Canonical request
// utils/s3-fixed.js (excerpt)
const canonicalRequest = [
method,
path,
query,
canonicalHeaders + '\n',
signedHeaders,
payloadSha256,
].join('\n');
const hashedCanonicalRequest = hex(new Uint8Array(
await crypto.subtle.digest('SHA-256', new TextEncoder().encode(canonicalRequest))
));
Derive signing key
// Date scope: YYYYMMDD/region/service/aws4_request
const kDate = await hmac('AWS4' + secretAccessKey, date);
const kRegion = await hmac(kDate, region);
const kService = await hmac(kRegion, service);
const kSigning = await hmac(kService, 'aws4_request');
String to sign and signature
const stringToSign = [
'AWS4-HMAC-SHA256',
amzDate,
scope,
hashedCanonicalRequest,
].join('\n');
const signature = hex(new Uint8Array(
await crypto.subtle.sign('HMAC', await importKey(kSigning), encoder.encode(stringToSign))
));
Common pitfalls
- URL-encode path and query exactly like the server. Mismatched encodings break signatures.
- When downloading, prefer presigned URLs to avoid headers with non-ASCII filenames.
- Use
UNSIGNED-PAYLOAD
where acceptable to avoid hashing large bodies. - Always double-check
Host
,x-amz-content-sha256
, andx-amz-date
headers.