Step by step walk thru of Signed Upload via PHP and/or JS/Ajax

Comments

13 comments

  • Avatar
    David Grant

    +1 for James!!

    Going on my 6th day now desperately trying to connect your disjointed code blurbs. What does "add this code to your webpage" actually mean?? WHAT WEBPAGE??

    In fact, there isn't a single page on this website that can help build even the simplest locally working script. Whether using Node or PHP, API or SDKs the instructions (I believe?) make assumptions that users aren't aware of. And searching for an answer always leads to another rabbit hole.

    Lastly, too much time was put into customizations and alternative methods BEFORE finishing the example. The tutorials need to be gone over again and again until the devs realize that there are unbridgeable gaps with no final code or visual example to work from.

    Feel free to contact me for specifics if desired

    0
    Comment actions Permalink
  • Avatar
    Stephen Doyle

    Hi James,

    There isn't currently a step by step tutorial available for using the upload widget and signing the requests using some server-side code, but the documentation for the widget covers the client-side steps required and there's a helper method in the PHP SDK (and our other server-side SDKs) which can perform the signature generation for you. 

    If you take a look at the 'signed uploads' part of the upload widget documentation ( https://cloudinary.com/documentation/upload_widget#signed_uploads ) there's a guide explaining the steps required.

    To summarise, you need to either:

    1) Have the signature pre-prepared by your server and included in the HTML, so your javascript code can use it at upload time. This requires that you know which parameters the widget will be using when it sends the upload, so it's not suitable for some workflows like if the user will be cropping an image in the widget before upload

    2) Have your client-side code make a request to your server at upload time, providing the parameters that need to be signed and receiving a signature in response from your server

    For the second case, there's an example of what that client-side function would look like in the above documentation, and depending on what language and framework you're using, you can have your server-side endpoint take the provided parameters and pass them to a helper method in our SDK for signing, and return the signature to the client.

    Using our PHP SDK for example, there's an api_sign_request() method which takes the parameters and your API key and returns the signature to use.

    This is a relatively uncommon way of performing uploads, but many customers use it successfully and I'd be happy to help get it up and running for you. If you're hitting any issues implementing the above please let me know and I'd be happy to take a look.

    0
    Comment actions Permalink
  • Avatar
    Stephen Doyle

    Hi David,

    Could you let me know which examples you're using and where you ran into issues? I'd also recommend looking at the sample apps that are distributed with each SDK - they cover many basic operations like upload and generating delivery URLs which specify transformations, and in most cases, they have very few dependencies other than the Cloudinary SDK itself

    Thanks,

    Stephen

    0
    Comment actions Permalink
  • Avatar
    David Grant

    Stephen,
    TY for response. I think I'm missing something VERY basic about getting this to display the images on a webpage.

    In what file would this code be placed into? Is it i.e. 'index.js'?

    cloudinary.v2.uploader.upload("my_picture.jpg", 
      function(error, result) {console.log(result, error)});

    And this?

    cloudinary.image("sample.jpg", {alt: "A sample photo", className: "Samples"})

    I'm not understanding how to use the code above to generate the URLs to use in the website.

    Is there a basic, prebuilt index.html/index.php file that includes links to the proper JS files needed?

    Here's the bottomline...I have a PHP page and want to use cloudinary to (finally) create the single source of all of my portfolio projects. I'm most likely only looking to have responsiveness, keep the image names the same and add the meta ALT tag. From this info, are you able to tell me the best way to get this accomplished?

    I pray that you can see where I'm coming from. I've been at this for a WEEK and haven't gotten anywhere. Please answer very simply. I am very thankful for a response and do not want to bother you guys again...as evidenced by the amount of time I've tried on my own 

     

    0
    Comment actions Permalink
  • Avatar
    James

    Stephen, 

    Thanks for your help.  Even with all the documentation, I'll admit I still struggled with this for quite awhile, but I believe I finally have a basic working prototype for my use case, which was to use the media uploader and sign the image before upload so that I can utilize some of the more advanced features like overwriting existing images, etc.

    Here is my solution, which turns out to be pretty simple after all, and might be worth posting for others, but feel free to correct if you see any blatant errors (it seems to be working):

    Client-facing Page (like an index.html)

    <!doctype html>
    <html lang="en">

    <head>
    <title>Title</title>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"
    crossorigin="anonymous">
    </head>

    <body>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>

    <button id="upload_widget" class="cloudinary-button">Upload files</button>

    <script src="https://widget.cloudinary.com/v2.0/global/all.js" type="text/javascript"></script>

    <script type="text/javascript">
    var generateSignature = function (callback, params_to_sign) {
    $.ajax({
    url: "/cloudinary/test.php", //This is the URL to your php script to receive the ajax data
    type: "GET",
    dataType: "text",
    data: {data: params_to_sign},
    complete: function () {console.log("complete")},
    success: function (signature, textStatus, xhr) {callback(signature);},
    error: function (xhr, status, error) {console.log(xhr, status, error);
    }
    });
    }
    </script>

    <script>
    var myWidget = cloudinary.createUploadWidget({
    cloudName: "Enter your cloudname here",
    uploadPreset: "Enter your preset here", //This is something you create inside your cloudinary account.
    public_Id: "Enter a customer public_id here if you want",
    apiKey: "Enter your api key here",
    cropping: true, //Optional Parameter
    showPoweredBy: false, //Optional Parameter
    clientAllowedFormats: ["png", "gif", "jpeg", "jpg"], //Optional Parameter
    uploadSignature: generateSignature
    }, (error, result) => {});

    document.getElementById("upload_widget").addEventListener("click", function () {
    myWidget.open();
    }, false);
    </script>

    </body>

    </html>

    PHP Script to Sign the Image (in this case test.php from above)

    <?php
    function api_sign_request($params_to_sign, $api_secret)
    {
    $params = array();
    foreach ($params_to_sign as $param => $value) {
    if (isset($value) && $value !== "") {
    if (!is_array($value)) {
    $params[$param] = $value;
    } else {
    if (count($value) > 0) {
    $params[$param] = implode(",", $value);
    }
    }
    }
    }
    ksort($params);
    $join_pair = function ($key, $value) {
    return $key . "=" . $value;
    };
    $to_sign = implode("&", array_map($join_pair, array_keys($params), array_values($params)));
    return sha1($to_sign . $api_secret);
    }

    $params_to_sign=$_GET['data'];
    $api_secret="Enter Your API Secret Here";

    echo api_sign_request($params_to_sign, $api_secret);
    ?>

    2
    Comment actions Permalink
  • Avatar
    Mohan Embar

    I wanted to thank James for this and echo his sentiment that I was banging my head against the wall trying to mimic this simple use case until I stumbled onto this user-contributed solution. I'm also scratching my head as to why Cloudinary considers this an uncommon use case. I'm trying to develop an SaaS app which has a Profile section where the user can upload and crop their avatar and the app can display it at different resolutions. It seems like a no-brainer that we'd want a signed upload here to prevent the universe from using my account as their own image repository. Am I reasoning about this incorrectly? So far, not 100% sold on Cloudinary for this given the lack of documentation and intelligence around this use case, but you all can thank James for keeping me in this fight for at least another day while I try his sample out.

    0
    Comment actions Permalink
  • Avatar
    Stephen Doyle

    Hi Mohan,

    Thank you for the feedback.

    The reason that this case is uncommon is that it requires more integration work than the other two methods: you need a server-side component to provide the signature, but also have to handle getting that signature back to the client-side code for the widget to use. It's far more common to either

    1) Allow unsigned upload directly from the browser or widget, or

    2) Handle the upload mostly or entirely on the server-side (e.g. a form that submits the image to the server, then your server-side code performs the upload)

    Regarding unsigned-uploads allowing users to use options that aren't part of your workflow, that's the reason that unsigned uploads are only able to use the options you pre-select in the upload preset and why they can't, for example, overwrite existing files. Unsigned uploads are intended to allow you to safely support uploads without server-side code needing to be involved for lightweight integrations like the upload widget or directly uploading from the user's browser

    You can choose exactly what happens to all images uploaded using a preset: how they're stored, if they're transformed with an incoming transformation before upload (e.g. adding watermarks, cropping, reducing the size, etc), and which access control options are used (e.g. making them inaccessible without a signature which needs to come from your server-side code).

    Many customers also use the notification_url option to receive a server-side notification of all uploaded files, then perform their own operations based on that callback, including removing files if they're part of a failed workflow or no longer needed - there's more information about that here: https://cloudinary.com/documentation/upload_images#eager_asynchronous_transformations 

    Please let me know if there's anything you're having trouble with, either here or via a support request, and I'd be happy to assist.

    Regards,

    Stephen

     

    0
    Comment actions Permalink
  • Avatar
    Eliot

    I'll have to agree with Mohan. Documentation is extremely frustrating.

    As he was saying, without a signed upload, anyone can upload as much as they want to your repository.

     

    I've pieced together what I think should work but it is still saying I need a preset for unsigned uploads

     

    // grab a current UNIX timestamp
    const millisecondsToSeconds = 1000;
    const timestamp = Math.round(Date.now() / millisecondsToSeconds);
    // generate the signature using the current timestmap and any other desired Cloudinary params
    const signature = cloudinaryV2.utils.api_sign_request({ timestamp }, CLOUDINARY_SECRET_KEY);
    // craft a signature payload to send to the client (timestamp and signature required)
    return signature;

    also tried

    return {
    signature,
    timestamp,
    };
    const generateSignature = async (callback: Function, params_to_sign: object): Promise<void> => {
    try {
    const signature = await generateSignatureCF({ slug: 'xxxx' });
    callback(signature);
    } catch (err) {
    console.log(err);
    }
    };
    cloudinary.openUploadWidget(
    {
    cloudName: 'xxx',
    uploadPreset: 'xxxx',
    sources: ['local', 'url', 'facebook', 'dropbox', 'google_photos'],
    folder: 'xxxx',
    apiKey: ENV.CLOUDINARY_PUBLIC_KEY,
    uploadSignature: generateSignature,
    },
    function(error, result) {
    console.log(error);
    },
    );
    0
    Comment actions Permalink
  • Avatar
    Eliot

    Figured it out. the function should return a string, not an object like it seems in the docs -_-

     

    Here is the implementation with a Firebase Cloud Function

    import * as functions from 'firebase-functions';
    import cloudinary from 'cloudinary';
    const CLOUDINARY_SECRET_KEY = functions.config().cloudinary.key;
    const cloudinaryV2 = cloudinary.v2;
    module.exports.main = functions.https.onCall(async (data, context: CallableContext) => {
      // Checking that the user is authenticated.
      if (!context.auth) {
        // Throwing an HttpsError so that the client gets the error details.
        throw new functions.https.HttpsError(
          'failed-precondition',
          'The function must be called while authenticated.',
        );
      }
      try {
        return cloudinaryV2.utils.api_sign_request(data.params_to_sign, CLOUDINARY_SECRET_KEY);
      } catch (error) {
        throw new functions.https.HttpsError('failed-precondition', error.message);
      }
    });
    
    
    // CLIENT
    const uploadWidget = () => {
      const generateSignature = async (callback: Function, params_to_sign: object): Promise<void> => {
        try {
          const signature = await generateImageUploadSignatureCF({ params_to_sign });
          callback(signature.data);
        } catch (err) {
          console.log(err);
        }
      };
    
      cloudinary.openUploadWidget(
        {
          cloudName: 'xxxxxx',
          uploadSignature: generateSignature,
          apiKey: ENV.CLOUDINARY_PUBLIC_KEY,
        },
        function(error, result) {
          console.log(error);
        },
      );
    };

     

    0
    Comment actions Permalink
  • Avatar
    Stephen Doyle

    Hi Eliot,

    Thanks for the Firebase function example; may I ask which documentation you were using which gave an example that didn't work, so I can check it?

    Regards,

    Stephen

    0
    Comment actions Permalink
  • Avatar
    Gray Reinhard

    Have to echo the general sentiment that Cloudinary's documentation is surprisingly weak and unclear...especially considering this is not an inexpensive subscription. Also, this particular thread is very much not an uncommon circumstance considering that in addition to the security of signed uploads, core features like overwriting files and eager transforms require signature. Getting frustrated with every extra minute I'm spending trying to get Cloudinary to fit my small set of image hosting needs.

    0
    Comment actions Permalink
  • Avatar
    Rebecca Peltz

    I've created a module in a new course that previews next week on Advanced Concepts.  I think you'll find this open-source code in the repo we're using helpful.  There are modules for signing both the upload and media library widgets. In addition, there is a server that does server side rendering of signed widgets and an index.html that uses client-side calls to APIs from the server.

    https://github.com/cloudinary-training/advanced-concepts/tree/master/signing-widgets/server

    Let me know if you have questions.  You should be able to set up an .env with your CLOUDINARY_URL and a USER_NAME that contains your email for the cloud account.  Then npm install and node app to start the server.

    Becky

     

    0
    Comment actions Permalink
  • Avatar
    Ganizani Chiwaura

    I also spent days trying to figure out Cloudinary's documentation. It is so badly done it's incredible. Thank God for this post. All we needed was a working, copy paste example. Duh!..... a million thank yous to the original poster who provided the solution. I wish I could buy you a drink 

    0
    Comment actions Permalink

Please sign in to leave a comment.