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.
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 };
}