Having issues trying to allow a client to upload a file via a presigned url.

Error received

<?xml version='1.0' encoding='UTF-8'?>
<Message>Access denied.</Message>
urlsigner@<project>.iam.gserviceaccount.com does not have storage.objects.create access to <bucket-name>/<filename>.pdf.

Code executes as a gcp cloud function and is used for generating. Cloud function is default service account function was updated to urlSigner

async createSignedUrl({
  }: SignedUrlRequest): Promise<string> {
    const bucket = await this.cloudStorage.bucket(BUCKET_NAME);
      moment.utc().add('minutes', 15).toDate(),
      Date.now() + 15 * 60 * 1000
    const options: any = {
      version: 'v4',
      action: 'write',
      expires: Date.now() + 15 * 60 * 1000,
    const [signedUrl] = await bucket

    return signedUrl;

CORS temp json file

    "maxAgeSeconds": 3600,
    "method": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
    "origin": ["*"],
    "responseHeader": [

Steps used to create gcp resources

gcloud iam service-accounts create urlsigner --display-name="GCS URL Signer" --project=<project-id>

gcloud iam service-accounts keys create service_account.json --iam-account=urlsigner@<project-id>.iam.gserviceaccount.com

gsutil mb gs://<bucket-name>

gsutil iam ch serviceAccount:urlsigner@<project-id>.iam.gserviceaccount.com:roles/storage.admin gs://<bucket-name>

gsutil cors set cors-json-file.json gs://<bucket-name>

The upload code

export async function uploadDoc(preSignedUrl: string, file: File) {
  return await new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('PUT', preSignedUrl, true);
    xhr.onload = () => {
      const status = xhr.status;
      if (status === 200) {
        resolve('good to go');
      } else {
        console.log('failed to upload');
        reject('not good to go');

    xhr.onabort = () => {
      reject('aborted for idk');

    xhr.onerror = () => {
      reject('Failed for idk');

    xhr.setRequestHeader('Content-Type', file.type);
    const formData = new FormData();
    formData.append('verify-doc', file);

Lastly, verified that the cloud function has the Service-Token-Creator role necessary for signing the blob that gets sent back to the front-end and that under the bucket permissions the service account is listed with the storage admin role too. So at this point just scratching my head as to why I'm getting the error of access denied for lack of permissions by the service account.

Thanks in advance for whoever can assist.

PS. I referenced this article as well https://medium.com/google-cloud/upload-download-files-from-a-browser-with-gcs-signed-urls-and-signed-policy-documents-f66fff8e425

  • issued resolved itself without any changes by me. My guess something with the servers got off and my updates to the service account weren't being honored
    – James
    Nov 21, 2020 at 1:40

1 Answer 1


The error you get means that your Cloud Function service account is lacking the storage.objects.create permission.

In order to fix it, you can either give your service account a predefined role like Storage Object Creator or create a custom role with that permission.


I see that I missed that your service account has the Storage Admin role, which includes storage.objects.* permissions and thus should be more than enough.

I would recommend you to try the following options:

  1. Remove the Storage Admin role from you function's service account and then add it again. Make sure to wait for a couple of minutes so that the roles could propagate properly.
  2. If the first option doesn't work for you, try giving the function's service account the Storage Object Creator and Service Account Token Creator roles separately.

Let me know if it helps.

  • but doesnt storage admin include that anyway.
    – James
    Sep 21, 2020 at 14:08
  • Thanks for pointing that out, I've updated the answer. Please let me know if it helps
    – Deniss T.
    Sep 24, 2020 at 14:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.