R2: Create a presigned URL with Temporary access credentials

Hi! I'd like to know what's the proper way to create a presigned URL for an object with temporary access credentials. Is that even possible? This is my try with typescript:
export function generatePresignedURL(object: string, bucket: string, region: string, accessKeyId: string, secretAccessKey: string, sessionToken: string = ''): string {
const amzDate = new Date().toISOString().replace(/[:-]/g, '').replace(/\.\d{3}/, '');
const dateStamp = amzDate.slice(0, 8);
const algorithm: string = "AWS4-HMAC-SHA256";
const credentialScope: string = `${dateStamp}/${region}/s3/aws4_request`; // %2F is /
const expires = 3600; // 1 hour
const url: URL = new URL(`https://${import.meta.env.VITE_APP_R2_ACCOUNT_ID}.eu.r2.cloudflarestorage.com/${bucket}/${object}?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=${accessKeyId}%2F${encodeURIComponent(credentialScope)}&X-Amz-Date=${amzDate}&X-Amz-Expires=${expires}&X-Amz-SignedHeaders=host`);
if(sessionToken != '') {
url.searchParams.append('X-Amz-Security-Token', encodeURIComponent(sessionToken));
}
const host = url.hostname;
const canonicalUri = new URL(url).pathname;
const canonicalQueryString = new URL(url).searchParams.toString();
var signedHeaders = 'host';
var canonicalHeaders = `host:${host}\n`;
if(sessionToken != '') {
signedHeaders += ';x-amz-security-token';
canonicalHeaders += `x-amz-security-token:${encodeURIComponent(sessionToken)}\n`;
}
const canonicalRequest = `GET\n${canonicalUri}\n${canonicalQueryString}\n${canonicalHeaders}\n${signedHeaders}\nUNSIGNED-PAYLOAD`
const stringToSign = `AWS4-HMAC-SHA256\n${amzDate}\n${credentialScope}\n${CryptoJS.SHA256(canonicalRequest).toString()}`;
const signingKey = getSignatureKey(secretAccessKey, dateStamp, region, 's3');
const signature = CryptoJS.HmacSHA256(stringToSign, signingKey).toString();
const presignedUrl = url.toString() + `&X-Amz-Signature=${signature}`;

return presignedUrl;
}
export function generatePresignedURL(object: string, bucket: string, region: string, accessKeyId: string, secretAccessKey: string, sessionToken: string = ''): string {
const amzDate = new Date().toISOString().replace(/[:-]/g, '').replace(/\.\d{3}/, '');
const dateStamp = amzDate.slice(0, 8);
const algorithm: string = "AWS4-HMAC-SHA256";
const credentialScope: string = `${dateStamp}/${region}/s3/aws4_request`; // %2F is /
const expires = 3600; // 1 hour
const url: URL = new URL(`https://${import.meta.env.VITE_APP_R2_ACCOUNT_ID}.eu.r2.cloudflarestorage.com/${bucket}/${object}?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=${accessKeyId}%2F${encodeURIComponent(credentialScope)}&X-Amz-Date=${amzDate}&X-Amz-Expires=${expires}&X-Amz-SignedHeaders=host`);
if(sessionToken != '') {
url.searchParams.append('X-Amz-Security-Token', encodeURIComponent(sessionToken));
}
const host = url.hostname;
const canonicalUri = new URL(url).pathname;
const canonicalQueryString = new URL(url).searchParams.toString();
var signedHeaders = 'host';
var canonicalHeaders = `host:${host}\n`;
if(sessionToken != '') {
signedHeaders += ';x-amz-security-token';
canonicalHeaders += `x-amz-security-token:${encodeURIComponent(sessionToken)}\n`;
}
const canonicalRequest = `GET\n${canonicalUri}\n${canonicalQueryString}\n${canonicalHeaders}\n${signedHeaders}\nUNSIGNED-PAYLOAD`
const stringToSign = `AWS4-HMAC-SHA256\n${amzDate}\n${credentialScope}\n${CryptoJS.SHA256(canonicalRequest).toString()}`;
const signingKey = getSignatureKey(secretAccessKey, dateStamp, region, 's3');
const signature = CryptoJS.HmacSHA256(stringToSign, signingKey).toString();
const presignedUrl = url.toString() + `&X-Amz-Signature=${signature}`;

return presignedUrl;
}
13 Replies
lpares12
lpares12OP•6mo ago
I'm passing the accessKeyId, secretAccessKey and sessionToken. But it seems that the signature generated when containing X-Amz-Security-Token does not really work, since cloudlflare returns this when accessing the URL:
<Error>
<script/>
<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your secret access key and signing method. </Message>
</Error>
<Error>
<script/>
<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your secret access key and signing method. </Message>
</Error>
When I do with non-Temporary access credentials, without passing the session token, then it works. So I'm starting to wonder if the temporary credentials have no permissions to create presignedURLs? Btw, the temporary access credentials have read permissions to the resource. And I'm just trying to do a GET of the resource. And more info, when I use normal credentials (not temporary access credentials), if I add also the token, when using the presigned URL I see:
<Error>
<script/>
<Code>InvalidArgument</Code>
<Message>Invalid Argument: X-Amz-Security-Token</Message>
</Error>
<Error>
<script/>
<Code>InvalidArgument</Code>
<Message>Invalid Argument: X-Amz-Security-Token</Message>
</Error>
Which is expected I guess. But makes me think that the temporary access credentials have no permissions for this operation.
lpares12
lpares12OP•6mo ago
This is the endpoint I used to generate the credentials btw: https://developers.cloudflare.com/api/operations/r2-create-temp-access-credentials
Cloudflare API Documentation
Interact with Cloudflare's products and services via the Cloudflare API
lpares12
lpares12OP•6mo ago
bump, still couldn't find a solution.. bump
harshil1712
harshil1712•6mo ago
Hey, did you find what you were looking for, or do you still need help?
lpares12
lpares12OP•6mo ago
Hi! I couldn't manage to do it. My guess is that you can't presign URLs with a temporary token (or you can but the server won't accept it). This is possible in AWS S3 btw
harshil1712
harshil1712•6mo ago
Hmm. Let me look into it and get back to you
lpares12
lpares12OP•5mo ago
No news about this?
harshil1712
harshil1712•5mo ago
I got stuck with something else. Looking into this now. Alright, I have tested this and it works. I am using the @aws-sdk/s3-request-presigner lib to generate the Pre-Signed URL, and the @aws-sdk/client-s3 to execute the S3 compatible commands. While reading about the temporary access credentials, I learned that I need to pass the Secret Key as well. However, when I generate the Temporary Access Credentials, I noticed that it also generated a new Secret Key. You will have to use this new Secret Key with the generated Temporary Access Token for a successful request. I hope this helps
lpares12
lpares12OP•5mo ago
Could you share the code? It does not work for me. The R2 server does not accept a X-Amz-Security-Token parameter. Hence, it seems to not follow the S3 specification. I of course, use the new Secret Key, Secret Key Id and the token Did you generatw the temporary credentials with the R2 endpoint? Are they for a specific bucket or for global access?
harshil1712
harshil1712•5mo ago
Yes, I created the credential using r2/temp-access-credentials endpoint. Here's the body I send with the request:
{
"bucket": "BUCKET_NAME",
"parentAccessKeyId": "ACCESS_ID",
"permission": "object-read-write",
"ttlSeconds": 6400
}
{
"bucket": "BUCKET_NAME",
"parentAccessKeyId": "ACCESS_ID",
"permission": "object-read-write",
"ttlSeconds": 6400
}
lpares12
lpares12OP•5mo ago
I have the exact same. The parent key has full control of R2, but R2 keeps replying that X-Amz-Security-Token is not allowed. Or just gives a CORS error (but I found out google chrome shows all errors as CORS errors 😅 ). I'd be great to see an example of how you are using this. Could it be the object I'm trying to access is "inside a folder" (e.g. whatever/whatever2/object.png). Even though I have tried in the "root" of the bucket and had the same problem I believe. Could I have the code you use to generate the presigned URL?
harshil1712
harshil1712•5mo ago
I am using this @aws-sdk/s3-request-presigner lib to generate the presigned URL. In your code, are you passing the new Secret Key or using the one you generated earlier? This was something that I found a bit tricky
lpares12
lpares12OP•4mo ago
I'm using all the data that R2 sends back from the r2/temp-access-credentials api call. So I'm using all the new secret key, id and token. I'm gonna try with the package later and see if I make any difference. But the same exact function I used for R2 worked fine in S3. Really late (I have been working on other stuff), but I confirm that with this package it works! I don't remember which package I used before or how I used it, so I can't really compare. And there must be something wrong in my custom function when adding the temporary token info, but In any case, I can work from this now!
Want results from more Discord servers?
Add your server