Skip to main content

[iOS] Signed upload fails: "Upload preset must be specified when using unsigned upload"

Comments

16 comments

  • Official comment
    Rebecca Peltz

    Your backend looks like it's set up correctly and all of your credentials are hidden.  Note that the API_SECRET is the credential that most corresponds to a password, and should therefore never be included in your front end code.  The API_KEY, which more corresponds to a username can be included in your front end code without the security risk.  

    What Stephen suggested in the earlier posts is that the API_KEY be included in the front end code

    , so the iOS front end configuration command would look like this:

    let config = CLDConfiguration(cloudName: "xxx",apiKey:"xxx", secure: true)

    This key is part of the signature, but is also needed in the front end config.

  • Stephen Doyle

    Hi,

    In the example you provided, it looks like you haven't provided the API key and secret in the SDK configuration, so the SDK code is unable to sign your request, and that's why the API call fails.
    Although you've added it to the request parameters, that doesn't allow the SDK code to access it for signing purposes.

    You can do something like this to configure the SDK with the API secret

        CLDConfiguration(cloudName: "test123", apiKey: "a", apiSecret: "b", secure: true);

    Separately, may I ask what is the reason for performing a signed upload from iOS Code? If this is for testing or internal purposes that's OK, but If you were to put the API key into client code, anyone who has access to the app would be able to extract the key from the app's code and make requests on your behalf to our API. 

    Regards,
    Stephen

    -2
  • Devis Battisti

    it's a direct upload, the signature is created from the backend.

    0
  • Rawfish

    Hi Stephen, I followed the example provided here: https://cloudinary.com/documentation/ios_image_and_video_upload
    If I use the configuration with the secret keys, it works, but it's not a recomended and secure option, as suggested by Cloudinary.

    example of signed upload by Cloudinary:

    let params = CLDUploadRequestParams()
      .setPublicId("newId")
    var mySig = MyFunction(params)  // your own function that returns a signature generated on your backend 
    params.setSignature(CLDSignature(signature: mySig.signature, timestamp: mySig.timestamp))
    let request = cloudinary.createUploader().signedUpload(
      url: "imageFile.jpg", params: params)
    0
  • Stephen Doyle

    Hi again,

    May I ask you to please try to set the API key using CLDConfiguration, then set the signature in the params for the uploader in the same manner as the in your first code sample and the documentation?

    Many of our SDK methods will discard parameters that they're not expecting, so I suspect the API Key isn't being passed through to the API call using the method in your first message, but should be passed if the SDK is configured with it via CLDConfiguration.

    Thanks,

    Stephen

    1
  • Rawfish

    Thanks, I've tried and finally it worked.

    Thank you very much Stephen!

    0
  • Marc Dinkel

    Hi Stephen
    I have the same problem but can't figure it out how to solve it. I am trying to make a signed request.

    In my iOS App I am making a GET request to my node.js backend to get the signature and timestamp. This looks something like this:

    const cloudinary = require('cloudinary').v2;
    cloudinary.config({
    cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
    api_key: process.env.CLOUDINARY_API_KEY,
    api_secret: process.env.CLOUDINARY_API_SECRET,
    })
    var timestamp = Math.round((new Date).getTime() / 1000);
    // Get the signature using the Node.js SDK method api_sign_request
    var signature = cloudinary.utils.api_sign_request({
    timestamp: timestamp,
    upload_preset: "presetname"
    }, process.env.CLOUDINARY_API_SECRET); 

    return { signature, timestamp }

    After receiving the signature I try to upload it to Cloudinary:
    let config = CLDConfiguration(cloudName: "xxx", secure: true)
    let cloudinary = CLDCloudinary(configuration: config)
    CLDCloudinary.logLevel = .debug
    let params = CLDUploadRequestParams()
    .setUploadPreset("presetname")

    params.setResourceType(.video) // set resource type
    params.setSignature(CLDSignature(signature: signature, timestamp: NSNumber(value: timestamp)))

    // upload video
    cloudinary.createUploader().signedUpload(url: videoUrl, params: params, ... and so on

    It works if I put my API Key and Secret into CLDConfiguration like this:

    let config = CLDConfiguration(cloudName: "cloud name", apiKey: "xxx", apiSecret: "xxx", secure: true)

    But shouldn't API Key and Secret be hidden as Rawfish mentioned? 
    How can I upload without revealing the keys?

    Thanks in advance!

    0
  • Rebecca Peltz

    Your backend looks like it's set up correctly and all of your credentials are hidden.  Note that the API_SECRET is the credential that most corresponds to a password, and should therefore never be included in your front end code.  The API_KEY, which more corresponds to a username can be included in your front end code without the security risk.  

    What Stephen suggested in the earlier posts is that the API_KEY be included in the front end code

    , so the iOS front end configuration command would look like this:

    let config = CLDConfiguration(cloudName: "xxx",apiKey:"xxx", secure: true)
    This key is part of the signature, but is also needed in the front end.

    1
  • Marc Dinkel

    Hi Rebecca
    Ohh now I get it! Thank u so much, it works now :)

    0
  • Deven Patel

    Hi,

    I'm facing the same issue, I tried all the above code for front end for iOS. 

    My front end (iOS) code is as below :

     let params = CLDUploadRequestParams()

            params.setResourceType(resourceType)
            params.setPublicId("\(PREF.USER_ID.get(DefaultValue: "0") ?? "0")_\(Date().timeIntervalSince1970)")
           self.shared.generateSignedKey(eager: "", publicID: params.publicId ?? "") { (signedKey, timeStamp) in
                if timeStamp.count > 0 {
                    params.setSignature(CLDSignature(signature: signedKey, timestamp: NSNumber.init(value: Int64(timeStamp) ?? 0)))
                    params.setParam("api_key", value: CLOUD_APIKEY)
                    onComplete(cloudinary.createUploader().signedUploadLarge(url: url, params: params, chunkSize: 5 * 1024 * 1024), nil)
                } else {
                    onComplete(nil, signedKey)
                }
            }

    self.shared.generateSignedKey(eager: "", publicID: params.publicId ?? "")

    This function is used for generating signature from backend.

    But all time it gives me error like below:

    Type parameter is not allowed when using unsigned upload. Only upload_preset,callback,public_id,folder,tags,context,metadata,face_coordinates,custom_coordinates,source,filename_override upload parameters are allowed., Upload preset must be whitelisted for unsigned uploads

    0
  • Rebecca Peltz

    Thank you for sharing your code. I see you are using a cloudinary object in the call to onComplete:
    onComplete(cloudinary.createUploader().signedUploadLarge(url: url, params: params, chunkSize: 5 * 1024 * 1024), nil)

    It would be helpful to see how you configured the creation of this object. I think that the problem may be that the Config doesn't include the API_KEY. I can see that you send the API_KEY as one of the params of your `uploader().signedUploadLarge` function. I think you may also need to add the API_KEY to the Config function parameter list that is used to create the cloudinary object like this:

    let config = CLDConfiguration(cloudName: "CLOUD_NAME", secure: "true", API_KEY:"<your API KEY>")
    let cloudinary = CLDCloudinary(configuration: config)
    

    Don't worry about exposing the API_KEY in front end code as it is not a password. Think of it more like a username and therefore not a violation of security in the way that adding the API_SECRET would be.

    If this doesn't help solve your problem, please share how you configured your cloudinary object. Adding the API_KEY to the Config is what helped other developers who posted on this page.

    0
  • Deven Patel

    I have already included the API_KEY in configuration parameter like below:

    cloudinary = CLDCloudinary(configuration: CLDConfiguration(cloudName: CLOUD_NAME, apiKey: CLOUD_APIKEY, secure: true))
    0
  • Rebecca Peltz

    We tested the code you provided here with a signature generator function and it works.  Can you share the code you used in your signature generator function?

    0
  • Rebecca Peltz

    Looking more closely at your error: `Type parameter is not allowed when using unsigned upload`, it appears that your upload request is being processed as an unsigned upload.  This makes me wonder if your signature param is being created correctly.  Is it possible for you to log or debug the signature value returned from your generate signature function to make sure it looks valid?  I'm not sure which SDK you're using on the backend to generate your signature, but it should look something like the node.js code below. Make sure your timestamp, public id, and API_SECRET are all valid in the backend. Your code doesn't show how `signedKey` is generated.

    ```

    var signature = cloudinary.utils.api_sign_request({
        timestamp: timestamp,
        public_id: 'sample_image'}, process.env.API_SECRET);
    0
  • Deven Patel

    Thanks Rebecca for the suggestions and time you spent for finding out the issue. 

    We got it resolved now.

    You are right we have made mistakes in creating signature and now we are able to resolve it.

    0
  • Gala Folder IT

    Hi! I'm, having a similar issue here. I'm getting 502 - Bad Gateway when uploading an image to Cloudinary.
    For some reason, everything is working ok with Axios but when I try to use fetch from js, it returns the 502

    Here is a snippet of my code: 

    const CLOUDINARY_API_URL: string = `https://api.cloudinary.com/v1_1/${CLOUDINARY_CLOUD_NAME}`;

    const signData = await getSignUploadForm(); //endpoint getting signature, this is working
    const formData = new FormData();
    formData.append("file", img);
    formData.append("api_key", CLOUDINARY_API_KEY);
    formData.append("timestamp", signData.timestamp);
    formData.append("signature", signData.signature);


    const response: any = await fetch(CLOUDINARY_API_URL + '/auto/upload',
                  {
                      method: 'POST',
                      body: formData,
                      mode: 'no-cors',
                      headers: {
                          'Content-Type': 'multipart/form-data',
                      }
                  }).then((res) =>
                      console.log(res)
                  ).catch(e => {
                      console.log(e);
                    }) ;
    0

Post is closed for comments.