file-uploadnext.jscloudinaryimage-upload

Cloudinary Image Upload


I am using Cloudinary with NextJS. When I am trying to specify the folder name in sign.js, which will create a signature for my request, I am getting an "Unauthorized" error as a response. But without a folder name, it works fine.

sign.js - This is the file responsible for creation of signature.

const cloudinary = require("cloudinary").v2;

const sign = async (req, res) => {
  const timestamp = Math.round((new Date()).getTime() / 1000);

  const signature = cloudinary.utils.api_sign_request({
    timestamp: timestamp,
    folder: 'product'
  }, process.env.NEXT_PUBLIC_CLOUDINARY_SECRET);

  res.statusCode = 200;
  res.json({ signature, timestamp });
};

export default sign;

index.js - This is the main file.

import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
import { useState } from 'react';

export default function Home() {

    const [selectedFile, setSelectedFile] = useState();
    const [isFilePicked, setIsFilePicked] = useState(false);
    const [file, setFile] = useState();

    const changeHandler = (event) => {
        console.log(event.target.files[0]);
        setSelectedFile(event.target.files[0]);
        setFile(URL.createObjectURL(event.target.files[0]))
        setIsFilePicked(true);
    };

    const handleSubmission = async () => {
        const { signature, timestamp } = await getSignature();
        const formData = new FormData();
        const url = `https://api.cloudinary.com/v1_1/${process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME}/upload`
        formData.append('file', selectedFile)
        formData.append("signature", signature);
        formData.append("timestamp", timestamp);
        formData.append("api_key", process.env.NEXT_PUBLIC_CLOUDINARY_KEY);
        fetch(url, {
            method: 'POST',
            body: formData
        }).then((response) => response.json())
            .then((result) => {
                console.log(result);
            })
            .catch((error) => {
                console.error('Error:', error);
            });
    }

    return (
        <div className={styles.container}>
            <Head>
                <title>Tailwind-Next</title>
                <meta name="description" content="Generated by create next app" />
                <link rel="icon" href="/favicon.ico" />
            </Head>
            <div className={styles.main}>
                <div className="flex items-center justify-center w-[50%] mb-5">
                    {selectedFile == null ?
                        <label htmlFor="dropzone-file" className={`flex flex-col items-center justify-center w-full h-64 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-bray-800  hover:bg-gray-100 ${styles.dropzone}`}>
                            <div className="flex flex-col items-center justify-center pt-5 pb-6">
                                <svg aria-hidden="true" className="w-10 h-10 mb-3 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path></svg>
                                <p className="mb-2 text-sm text-gray-500 dark:text-gray-400"><span className="font-semibold">Click to upload</span> or drag and drop</p>
                                <p className="text-xs text-gray-500 dark:text-gray-400">SVG, PNG, JPG or GIF (MAX. 800x400px)</p>
                            </div>
                            <input id="dropzone-file" type="file" className="hidden" accept="image/*" onChange={changeHandler} />
                        </label> :
                        <div className='flex flex-col items-center justify-center w-full h-64 relative'>
                            <div className={styles.image_wrapper}>
                                <img src={file} className={styles.img_preview} alt="" />
                            </div>
                        </div>}

                </div>
                <button className="w-[50%] bg-blue-600 hover:bg-blue-500 text-white font-bold py-2 px-4 border-b-4 border-blue-700 hover:border-blue-600 rounded" onClick={handleSubmission}>
                    Upload
                </button>
            </div>

        </div>
    )

}

async function getSignature() {
    const response = await fetch("/api/sign");
    const data = await response.json();
    const { signature, timestamp } = data;
    return { signature, timestamp };
}

I tried creating a signature without a folder name, and it worked fine. However, when I attempted to create a signature using the folder name, it returned unauthorized. 


Solution

  • After 13 bad requests and 31 unauthorized requests, I was finally able to resolve the issue.

    sign.js - This is the file responsible for creation of signature.

    const cloudinary = require("cloudinary").v2;
    
    const sign = async (req, res) => {
      const timestamp = Math.round((new Date()).getTime() / 1000);
    
      const signature = cloudinary.utils.api_sign_request({
        timestamp: timestamp,
        folder: 'product'
      }, process.env.NEXT_PUBLIC_CLOUDINARY_SECRET);
    
      res.statusCode = 200;
      res.json({ signature, timestamp });
    };
    
    export default sign;
    

    index.js - This is the main file.

    import Head from 'next/head'
    import Image from 'next/image'
    import styles from '../styles/Home.module.css'
    import { useState } from 'react';
    
    export default function Home() {
    
        const [selectedFile, setSelectedFile] = useState();
        const [isFilePicked, setIsFilePicked] = useState(false);
        const [file, setFile] = useState();
    
        const changeHandler = (event) => {
            console.log(event.target.files[0]);
            setSelectedFile(event.target.files[0]);
            setFile(URL.createObjectURL(event.target.files[0]))
            setIsFilePicked(true);
        };
    
        const handleSubmission = async () => {
            const { signature, timestamp } = await getSignature();
            const formData = new FormData();
            const url = `https://api.cloudinary.com/v1_1/${process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME}/upload`
            formData.append('file', selectedFile)
            formData.append("signature", signature);
            formData.append("timestamp", timestamp);
            formData.append("api_key", process.env.NEXT_PUBLIC_CLOUDINARY_KEY);
            formData.append("folder", "product")
            fetch(url, {
                method: 'POST',
                body: formData
            }).then((response) => response.json())
                .then((result) => {
                    console.log(result);
                })
                .catch((error) => {
                    console.error('Error:', error);
                });
        }
    
        return (
            <div className={styles.container}>
                <Head>
                    <title>Tailwind-Next</title>
                    <meta name="description" content="Generated by create next app" />
                    <link rel="icon" href="/favicon.ico" />
                </Head>
                <div className={styles.main}>
                    <div className="flex items-center justify-center w-[50%] mb-5">
                        {selectedFile == null ?
                            <label htmlFor="dropzone-file" className={`flex flex-col items-center justify-center w-full h-64 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-bray-800  hover:bg-gray-100 ${styles.dropzone}`}>
                                <div className="flex flex-col items-center justify-center pt-5 pb-6">
                                    <svg aria-hidden="true" className="w-10 h-10 mb-3 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path></svg>
                                    <p className="mb-2 text-sm text-gray-500 dark:text-gray-400"><span className="font-semibold">Click to upload</span> or drag and drop</p>
                                    <p className="text-xs text-gray-500 dark:text-gray-400">SVG, PNG, JPG or GIF (MAX. 800x400px)</p>
                                </div>
                                <input id="dropzone-file" type="file" className="hidden" accept="image/*" onChange={changeHandler} />
                            </label> :
                            <div className='flex flex-col items-center justify-center w-full h-64 relative'>
                                <div className={styles.image_wrapper}>
                                    <img src={file} className={styles.img_preview} alt="" />
                                </div>
                            </div>}
    
                    </div>
                    <button className="w-[50%] bg-blue-600 hover:bg-blue-500 text-white font-bold py-2 px-4 border-b-4 border-blue-700 hover:border-blue-600 rounded" onClick={handleSubmission}>
                        Upload
                    </button>
                </div>
    
            </div>
        )
    }
    
    async function getSignature() {
        const response = await fetch("/api/sign");
        const data = await response.json();
        const { signature, timestamp } = data;
        return { signature, timestamp };
    }