Question

Can I upload to Spaces using a signed url?

Hello,

Im currently developing a React webapp that lets users upload images. I have it working with AWS S3 but would like to have everything on DO for simplicities sake.

The flow goes like this:

  • Send image info to server and receive a signed url
  • Post the image to the signed url

When i try this with spaces im constantly getting a “SignatureDoesNotMatch” error.

Please help, im going batty!

Here’s my node server code:

spacesEndpoint = new aws.Endpoint(`${DO_REGION}.digitaloceanspaces.com`),
s3 = new aws.S3({
	endpoint: spacesEndpoint,
	accessKeyId: DO_ACCESS_KEY_ID,
	secretAccessKey: DO_SECRET_ACCESS_KEY,
	region: DO_REGION,
	signatureVersion: 'v4',
});

const s3Params = {
	Bucket: DO_SPACE,
	Expires: 60,
	Key: filePath,
	ContentType: fileType, // "image/jpeg"
	ACL: 'public-read',
};

const promise = new Promise((resolve, reject) => {
	s3.getSignedUrl('putObject', s3Params, (err, url) => {
		if (err) {
			reject(err);
		}
		resolve(url);
	});
});

and this is my javascript

const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('PUT', payload.signedUrl);
xhr.setRequestHeader('Host', `${DO_SPACE}.${DO_REGION}.digitaloceanspaces.com');
xhr.setRequestHeader('x-amz-acl', 'public-read');
xhr.setRequestHeader('Content-Type', payload.file.type);
xhr.setRequestHeader('Content-Length', payload.file.size);
xhr.send(payload.file);
Subscribe
Share

Submit an answer
You can type!ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

These answers are provided by our Community. If you find them useful, show some love by clicking the heart. If you run into issues leave a comment, or add your own answer to help others.

Hello

So i was able to get it workign with the CORS configuration you recommended, and the following server and client code.

Thank you very much for your help.

const aws = require('aws-sdk');
spacesEndpoint = new aws.Endpoint(`${DO_REGION}.digitaloceanspaces.com`),
s3 = new aws.S3({
	endpoint: spacesEndpoint,
	accessKeyId: DO_ACCESS_KEY_ID,
	secretAccessKey: DO_SECRET_ACCESS_KEY,
	region: DO_REGION,
	signatureVersion: 'v4',
});

const s3Params = {
	Bucket: DO_SPACE,
	Key: filePath,
	ContentType: fileType,
	ACL: 'public-read',
	Expires: 60,
};

s3.getSignedUrl('putObject', s3Params, (err, url) => {
	if (err) {
		reject(err);
	}

	resolve(url);
});
const xhr = new XMLHttpRequest();
xhr.open('PUT', signedUrl);
xhr.setRequestHeader('Content-Type', file.type);
xhr.setRequestHeader('x-amz-acl', 'public-read');
xhr.send(file);

@admin94a05aa0b9579d1a73a36 did this help? wanted to check if you made any progress.

OPTIONS is the HTTP method used for the CORS preflight request, to ask which other methods are permitted, based on the Access-Control-*, Origin headers that are sent, which are checked against the stores CORSConfiguration for a matching CORSRule.

A good group of settings for a single CORSRule during development:

  • Allowed Origin: *
  • AllowedMethod: (all of them)
  • AllowedHeader: * (important in dev, extra headers in SDKs can cause CORS preflight to fail)
  • MaxAgeSeconds: 600

AllowedHeader: * will help you the most I think, as I see Postman-Token in your request, you didn’t include that in the allowed headers you showed.

You said you got a SignatureDoesNotMatch in the original post, could you please reproduce that error and share the request/response headers&body for that instance?

  • Only an OPTIONS request and no PUT request?
  • Did you have CORS set up on the bucket?

Looks like your client is enforcing CORS, so you’ll need to do that first: https://www.digitalocean.com/docs/spaces/how-to/cors/ Since your Origin is Origin: http://0.0.0.0:3001, I recommend setting a CORS wildcard to start, then changing it to match your production deployment later.

P.S. The bucket name appears in multiple places, I see it leaked in your response.

Hi from Spaces Engineering team!

When using presigned URLs to upload, getting SignatureDoesNotMatch usually means that the headers that were used in signing don’t match the actual headers sent by the real request.

It would help tremendously if you could post one of the signed URLs, as well as the entire HTTP headers for both the request & response sent by the client using the presigned URL.

To avoid security issues, you should redact the following from them before posting:

  • AccessKey
  • Signature
  • BucketName (optional but recommended, because it might let somebody else to your user here to a specific bucket)
  • Filename (optional if sensitive, but useful in debugging)