{"id":28115,"date":"2022-05-22T16:15:16","date_gmt":"2022-05-22T16:15:16","guid":{"rendered":"http:\/\/signed-uploads-in-cloudinary-with-next.js"},"modified":"2025-02-25T15:53:27","modified_gmt":"2025-02-25T23:53:27","slug":"signed-uploads-in-cloudinary-with-next-js","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/","title":{"rendered":"Server-signed Uploads in Cloudinary with Next.js"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>By default, <a href=\"https:\/\/cloudinary.com\/\">Cloudinary<\/a> allows you to upload images by specifying an upload preset and a cloud name. Those details are not difficult to figure out, making it easy for hackers to compromise your account online. Luckily, Cloudinary provides a more secure upload method called <code>signed-upload<\/code>.<\/p>\n<p>Signed uploads include an additional parameter called <code>uploadSignature<\/code> along with the data of the image being uploaded and your API key. The upload signature is generated by a server upon request. It accepts the parameters you send from the client, signs the parameters using your API key, and then sends resulting upload signature back to the client.<\/p>\n<p>This allows you to upload assets securely to Cloudinary and protects your account from unauthorized access.<\/p>\n<p>Nevertheless, it can be overkill (with the requirement of setting up a server), which may not be necessary if you\u2019re building a client-side application to demonstrate functionality &#8211; in which case an unsigned upload would suffice.<\/p>\n<p>In this post, I\u2019ll walk us through:<\/p>\n<ul>\n<li>How to handle unsigned uploads to Cloudinary in Next.js,<\/li>\n<li>Describe potential vulnerabilities it may present, and<\/li>\n<li>Show how to account for it with a Signed upload.<\/li>\n<\/ul>\n<h2>Setup and Installations<\/h2>\n<p>Create a new Next.js application named <strong>cloudinary-next-demo<\/strong> by running the following command in the terminal:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npx create-next-app cloudinary-next-demo\n<\/code><\/span><\/pre>\n<p>This will create a <code>cloudinary-next-demo<\/code> application for you. After that, change directory into the <strong>cloudinary-next-demo<\/strong> project you created with the command:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-comment\"># Navigate into the project directory<\/span>\n\ncd cloudinary-next-demo \n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Next, install the following Cloudinary npm package so we can easily use Cloudinary on the server:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm install cloudinary\n<\/code><\/span><\/pre>\n<h2>Setting up Cloudinary<\/h2>\n<p>As we are going to upload images to Cloudinary, we need to create an upload preset and set the upload mode to unsigned. To do that, log into your Cloudinary account and click on <strong>Settings &gt; Upload<\/strong>.\n<img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/media_jams\/s_7A4CF5DA5B0E63BC163E1954C171ACAAD309FD95DEE6429013A13CBA292D788C_1651779901902_settings.png\" alt=\"\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"1568\"\/><\/p>\n<p>Now, scroll down to upload presets and click on <strong>Add upload presets<\/strong>. Then:<\/p>\n<ul>\n<li>Provide an upload preset name.<\/li>\n<li>Set Signing Mode to unsigned, and<\/li>\n<li>Set a desired folder to hold the image uploads (optional).<\/li>\n<\/ul>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/media_jams\/s_7A4CF5DA5B0E63BC163E1954C171ACAAD309FD95DEE6429013A13CBA292D788C_1652453204350_cld-preset.png\" alt=\"\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"1568\"\/><\/p>\n<p>Lastly, open your projects <code>_document.js file<\/code> (create it if it doesn\u2019t exist) and update it with this snippet:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> Document, { Html, Head, Main, NextScript } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/document\"<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">MyDocument<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Document<\/span> <\/span>{\n  <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">async<\/span> getInitialProps(ctx) {\n    <span class=\"hljs-keyword\">const<\/span> initialProps = <span class=\"hljs-keyword\">await<\/span> Document.getInitialProps(ctx);\n    <span class=\"hljs-keyword\">return<\/span> { ...initialProps };\n  }\n\n  render() {\n    <span class=\"hljs-keyword\">return<\/span> (\n      <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Html<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Head<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span>\n            <span class=\"hljs-attr\">defer<\/span>\n            <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/widget.cloudinary.com\/v2.0\/global\/all.js\"<\/span>\n            <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"text\/javascript\"<\/span>\n          &gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Head<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">body<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Main<\/span> \/&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">NextScript<\/span> \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">body<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Html<\/span>&gt;<\/span><\/span>\n    );\n  }\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> MyDocument;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Here, we added the Cloudinary CDN to the <code>&lt;Head \/&gt;<\/code> tag so that we can create the Cloudinary upload widget using the browser <code>window<\/code> object.<\/p>\n<p>Next, run the development server with <code>npm run dev<\/code> to see the changes in the browser as we build the project.<\/p>\n<h2>Implement usigned uploads<\/h2>\n<p>Open the project folder in your preferred code editor and create a <strong>components<\/strong> folder in the root of the project. Inside it, create a new <code>ImageUpload.js<\/code> file and update it with this snippet:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ src\/components\/ImageUpload.js<\/span>\nimport { useState } from <span class=\"hljs-string\">\"react\"<\/span>;\nexport <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">ImageUpload<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> &#91;isImagUploaded, setIsImageUploaded] = useState(<span class=\"hljs-keyword\">false<\/span>);\n\n  async <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handleWidgetClick<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    <span class=\"hljs-keyword\">const<\/span> widget = window.cloudinary.createUploadWidget(\n      {\n        cloudName: <span class=\"hljs-string\">\"YOUR_CLOUD_NAME\"<\/span>,\n        uploadPreset: <span class=\"hljs-string\">\"YOUR_UNSIGNED_UPLOAD_PRESET\"<\/span>,\n        resourceType: <span class=\"hljs-string\">\"image\"<\/span>,\n      },\n      (error, result) =&gt; {\n        <span class=\"hljs-keyword\">if<\/span> (!error &amp;&amp; result &amp;&amp; result.event === <span class=\"hljs-string\">\"success\"<\/span>) {\n          console.log(<span class=\"hljs-string\">\"Uploaded\"<\/span>, result.info);\n          setIsImageUploaded(<span class=\"hljs-keyword\">true<\/span>);\n        } <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (error) {\n          console.log(error);\n        }\n      }\n    );\n\n    widget.open();\n  }\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    &lt;div&gt;\n      &lt;button type=<span class=\"hljs-string\">\"button\"<\/span> onClick={handleWidgetClick}&gt;\n        Upload Image\n      &lt;\/button&gt;\n\n      {isImagUploaded ? (\n        &lt;&gt;\n          &lt;div&gt;Successfully uploaded&lt;\/div&gt;\n        &lt;\/&gt;\n      ) : <span class=\"hljs-keyword\">null<\/span>}\n    &lt;\/div&gt;\n  );\n}\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This is all you need to upload images to Cloudinary using the unsigned upload method. If you save the project, you should see that uploading to Cloudinary works as expected on the browser.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1692974134\/upload-demo_manp49.webp\" alt=\"unsigned upload\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"800\" height=\"628\"\/><\/p>\n<h2>Security concerns<\/h2>\n<p>The parameters we passed to the Cloudinary upload widget to make the upload possible is the <code>cloudname<\/code>, the <code>upload preset<\/code> and the <code>resourceType<\/code>. As a result, if the upload preset is known to someone, they can use it to upload resources to our account without authorization.<\/p>\n<p>They can\u2019t edit, override or delete content from our account, however, if the issue is not caught on time, they could max out our resources and incur debts for us.<\/p>\n<h2>What to do?<\/h2>\n<p>First, we can remedy the situation easily by changing the upload preset name from our Cloudinary console.<\/p>\n<p>Better still, we can prevent it from happening at all by using the signed upload method.<\/p>\n<p>Signed upload adds an extra layer of security by requiring an upload signature that is generated on a server using your Cloudinary API secret. That would solve the security concern we have with the unsigned upload method.<\/p>\n<h2>Impelement Cloudinary signed upload<\/h2>\n<p>First, we need to create a utility function that would make a POST request to generate an upload signature from our server. To do that, create a <code>utils<\/code> folder on the root of the project and create a new <code>generateSignature.js<\/code> file. Update the file with the snippet below:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">generateSignature<\/span>(<span class=\"hljs-params\">callback, paramsToSign<\/span>) <\/span>{\n  fetch(<span class=\"hljs-string\">`\/api\/sign`<\/span>, {\n    <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n    <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({\n      paramsToSign,\n    }),\n  })\n    .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">r<\/span>) =&gt;<\/span> r.json())\n    .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">{ signature }<\/span>) =&gt;<\/span> {\n      callback(signature);\n    });\n}\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Here, we are making a POST request to the <code>api\/sign<\/code> route where our server lives. The <code>callback<\/code> parameter will be used to process the signature returned from the server while the <code>paramsToSign<\/code> parameter will represent the data we are sending to the server to be signed.<\/p>\n<p>With this, we can set up our server in the specified <code>pages\/api\/sign<\/code> route. Create the <code>sign.js<\/code> file in the <code>pages\/api<\/code> folder and add this snippet to it:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { v2 <span class=\"hljs-keyword\">as<\/span> cloudinary } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"cloudinary\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handler<\/span>(<span class=\"hljs-params\">req, res<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> body = <span class=\"hljs-built_in\">JSON<\/span>.parse(req.body) || {};\n  <span class=\"hljs-keyword\">const<\/span> { paramsToSign } = body;\n\n  <span class=\"hljs-keyword\">const<\/span> apiSecret = <span class=\"hljs-string\">\"YOUR_CLOUDINARY_API_SECRET\"<\/span>;\n\n  <span class=\"hljs-keyword\">try<\/span> {\n    <span class=\"hljs-keyword\">const<\/span> signature = cloudinary.utils.api_sign_request(\n      paramsToSign,\n      apiSecret\n    );\n    res.json({ signature });\n  } <span class=\"hljs-keyword\">catch<\/span> (error) {\n    <span class=\"hljs-built_in\">console<\/span>.log(error);\n    res.send(error);\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>When a request comes in from the client, we destructure the <code>paramsToSign<\/code> object out of the request body and sign it using the Cloudinary utility function for signing upload signatures <code>cloudinary.utils.api_sign_request()<\/code>. Then send back the signed signature to the client making the request.<\/p>\n<p>Next, we need to update our Cloudinary upload widget in the <code>ImageUpload.js<\/code> file to include the <code>uploadSignature<\/code> parameter and set the value to our <code>generateSignature<\/code> function.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ src\/components\/ImageUpload.js<\/span>\nimport { useState } from <span class=\"hljs-string\">\"react\"<\/span>;\nimport { generateSignature } from <span class=\"hljs-string\">\"..\/utils\/generateSignature\"<\/span>;\nexport <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">ImageUpload<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> &#91;isImagUploaded, setIsImageUploaded] = useState(<span class=\"hljs-keyword\">false<\/span>);\n\n  async <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handleWidgetClick<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    <span class=\"hljs-keyword\">const<\/span> widget = window.cloudinary.createUploadWidget(\n      {\n        cloudName: <span class=\"hljs-string\">\"YOUR_CLOUD_NAME\"<\/span>,\n        apiKey: <span class=\"hljs-string\">\"YOUR_CLOUDINARY_API_KEY\"<\/span>,\n        uploadSignature: generateSignature,\n        resourceType: <span class=\"hljs-string\">\"image\"<\/span>,\n      },\n      (error, result) =&gt; {\n        <span class=\"hljs-keyword\">if<\/span> (!error &amp;&amp; result &amp;&amp; result.event === <span class=\"hljs-string\">\"success\"<\/span>) {\n          console.log(<span class=\"hljs-string\">\"Uploaded\"<\/span>, result.info);\n          setIsImageUploaded(<span class=\"hljs-keyword\">true<\/span>);\n        } <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (error) {\n          console.log(error);\n        }\n      }\n    );\n    widget.open();\n  }\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    &lt;div&gt;\n      &lt;button type=<span class=\"hljs-string\">\"button\"<\/span> onClick={handleWidgetClick}&gt;\n        Upload Image\n      &lt;\/button&gt;\n      {isImagUploaded ? (\n        &lt;&gt;\n          &lt;div&gt;Successfully uploaded&lt;\/div&gt;\n        &lt;\/&gt;\n      ) : <span class=\"hljs-keyword\">null<\/span>}\n    &lt;\/div&gt;\n  );\n}\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>In the snippet above, we first import the <code>generateSignature<\/code> function from the utilities folder. Then, we:<\/p>\n<ul>\n<li>Add the <code>uploadSignature<\/code> and the parameter to the upload widget and set it to the <code>generateSignature<\/code> function.<\/li>\n<li>Add the Cloudinary API key to the upload widget parameters.<\/li>\n<li>Remove the upload preset param as we no longer need it.<\/li>\n<\/ul>\n<p>Now if you click the button to upload an image to Cloudinary again, it should work as an authorized request to the server and your image should be uploaded successfully as seen below.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1692974134\/signed-upload_g2aixb.webp\" alt=\"signed-upload-demo\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"800\" height=\"618\"\/><\/p>\n<h2>Conclusion<\/h2>\n<p>In this article, we\u2019ve successfully demonstrated how to implement the two Cloudinary upload methods (signed and unsigned) uploads. We went over the security vulnerability of the unsigned method and also demonstrated how we can solve that vulnerability using the signed upload method. If you\u2019d like to read more about this, see the docs or play with the code here on <a href=\"https:\/\/github.com\/kenny-io\/signed-upload-demo\">Github<\/a><\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":28116,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[134,212,371,373],"class_list":["post-28115","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-guest-post","tag-next-js","tag-under-review","tag-upload"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Server-signed Uploads in Cloudinary with Next.js<\/title>\n<meta name=\"description\" content=\"Signed uploads allow developers to securely upload files to Cloudinary using a server-generated upload signature. In this post, we&#039;ll explore the vulnerabilities provided by unsigned uploads and how we can mitigate them with signed uploads.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Server-signed Uploads in Cloudinary with Next.js\" \/>\n<meta property=\"og:description\" content=\"Signed uploads allow developers to securely upload files to Cloudinary using a server-generated upload signature. In this post, we&#039;ll explore the vulnerabilities provided by unsigned uploads and how we can mitigate them with signed uploads.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2022-05-22T16:15:16+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-02-25T23:53:27+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1681923980\/Web_Assets\/blog\/3aa37f1248ecc05999e49dd32d1296225883626a-2560x1440-1_2811646424\/3aa37f1248ecc05999e49dd32d1296225883626a-2560x1440-1_2811646424-png?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"2560\" \/>\n\t<meta property=\"og:image:height\" content=\"1440\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"Server-signed Uploads in Cloudinary with Next.js\",\"datePublished\":\"2022-05-22T16:15:16+00:00\",\"dateModified\":\"2025-02-25T23:53:27+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/\"},\"wordCount\":7,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681923980\/Web_Assets\/blog\/3aa37f1248ecc05999e49dd32d1296225883626a-2560x1440-1_2811646424\/3aa37f1248ecc05999e49dd32d1296225883626a-2560x1440-1_2811646424.png?_i=AA\",\"keywords\":[\"Guest Post\",\"Next.js\",\"Under Review\",\"Upload\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2022\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/\",\"url\":\"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/\",\"name\":\"Server-signed Uploads in Cloudinary with Next.js\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681923980\/Web_Assets\/blog\/3aa37f1248ecc05999e49dd32d1296225883626a-2560x1440-1_2811646424\/3aa37f1248ecc05999e49dd32d1296225883626a-2560x1440-1_2811646424.png?_i=AA\",\"datePublished\":\"2022-05-22T16:15:16+00:00\",\"dateModified\":\"2025-02-25T23:53:27+00:00\",\"description\":\"Signed uploads allow developers to securely upload files to Cloudinary using a server-generated upload signature. In this post, we'll explore the vulnerabilities provided by unsigned uploads and how we can mitigate them with signed uploads.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681923980\/Web_Assets\/blog\/3aa37f1248ecc05999e49dd32d1296225883626a-2560x1440-1_2811646424\/3aa37f1248ecc05999e49dd32d1296225883626a-2560x1440-1_2811646424.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681923980\/Web_Assets\/blog\/3aa37f1248ecc05999e49dd32d1296225883626a-2560x1440-1_2811646424\/3aa37f1248ecc05999e49dd32d1296225883626a-2560x1440-1_2811646424.png?_i=AA\",\"width\":2560,\"height\":1440},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Server-signed Uploads in Cloudinary with Next.js\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Server-signed Uploads in Cloudinary with Next.js","description":"Signed uploads allow developers to securely upload files to Cloudinary using a server-generated upload signature. In this post, we'll explore the vulnerabilities provided by unsigned uploads and how we can mitigate them with signed uploads.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/","og_locale":"en_US","og_type":"article","og_title":"Server-signed Uploads in Cloudinary with Next.js","og_description":"Signed uploads allow developers to securely upload files to Cloudinary using a server-generated upload signature. In this post, we'll explore the vulnerabilities provided by unsigned uploads and how we can mitigate them with signed uploads.","og_url":"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/","og_site_name":"Cloudinary Blog","article_published_time":"2022-05-22T16:15:16+00:00","article_modified_time":"2025-02-25T23:53:27+00:00","og_image":[{"width":2560,"height":1440,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1681923980\/Web_Assets\/blog\/3aa37f1248ecc05999e49dd32d1296225883626a-2560x1440-1_2811646424\/3aa37f1248ecc05999e49dd32d1296225883626a-2560x1440-1_2811646424-png?_i=AA","type":"image\/png"}],"twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/"},"author":{"name":"","@id":""},"headline":"Server-signed Uploads in Cloudinary with Next.js","datePublished":"2022-05-22T16:15:16+00:00","dateModified":"2025-02-25T23:53:27+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/"},"wordCount":7,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681923980\/Web_Assets\/blog\/3aa37f1248ecc05999e49dd32d1296225883626a-2560x1440-1_2811646424\/3aa37f1248ecc05999e49dd32d1296225883626a-2560x1440-1_2811646424.png?_i=AA","keywords":["Guest Post","Next.js","Under Review","Upload"],"inLanguage":"en-US","copyrightYear":"2022","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/","url":"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/","name":"Server-signed Uploads in Cloudinary with Next.js","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681923980\/Web_Assets\/blog\/3aa37f1248ecc05999e49dd32d1296225883626a-2560x1440-1_2811646424\/3aa37f1248ecc05999e49dd32d1296225883626a-2560x1440-1_2811646424.png?_i=AA","datePublished":"2022-05-22T16:15:16+00:00","dateModified":"2025-02-25T23:53:27+00:00","description":"Signed uploads allow developers to securely upload files to Cloudinary using a server-generated upload signature. In this post, we'll explore the vulnerabilities provided by unsigned uploads and how we can mitigate them with signed uploads.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681923980\/Web_Assets\/blog\/3aa37f1248ecc05999e49dd32d1296225883626a-2560x1440-1_2811646424\/3aa37f1248ecc05999e49dd32d1296225883626a-2560x1440-1_2811646424.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681923980\/Web_Assets\/blog\/3aa37f1248ecc05999e49dd32d1296225883626a-2560x1440-1_2811646424\/3aa37f1248ecc05999e49dd32d1296225883626a-2560x1440-1_2811646424.png?_i=AA","width":2560,"height":1440},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/signed-uploads-in-cloudinary-with-next-js\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Server-signed Uploads in Cloudinary with Next.js"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":""}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681923980\/Web_Assets\/blog\/3aa37f1248ecc05999e49dd32d1296225883626a-2560x1440-1_2811646424\/3aa37f1248ecc05999e49dd32d1296225883626a-2560x1440-1_2811646424.png?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28115","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=28115"}],"version-history":[{"count":3,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28115\/revisions"}],"predecessor-version":[{"id":37024,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28115\/revisions\/37024"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/28116"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=28115"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=28115"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=28115"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}