Build a file storage system with Cloudinary + Auth0
Tags: cloudinary, hackjamstack, auth0, jamstack
Published: 2021-10-17 11:27:20

---
title: Build a file storage system with Cloudinary + Auth0
published: true
description: 
tags: #cloudinary #hackjamstack #auth0 #jamstack
cover_image: !(https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ja0lo8x001l5gyx7iy1q.png)
---
File hosting and synchronization services like Google Drive, OneDrive, iCloud, e.t.c allow users to store files in the cloud, synchronize files across devices, and share files. They help save space on devices by digitizing files, integrating storage, and supporting remote access.

In this post, we will discuss how to build a personalized and secure file storage system using [Cloudinary](https://cloudinary.com/), [Auth0](https://auth0.com/), and [Next.js](https://nextjs.org/). At the end of this tutorial, we would have learnt how to host files on Cloudinary by tag, serve files and secure the web application using Auth0.

By way of introduction, Cloudinary is a platform on which you can quickly and easily upload, store, manage, transform, and deliver images and videos for websites and apps. The platform also offers a vast collection of SDKs for frontend frameworks and libraries. 

Auth0 is a platform that offers systems to rapidly integrate authentication and authorization for web, mobile, and legacy applications.

Next.js is a React-based front-end development framework that enables functionalities like server-side rendering, static site generation, file-system routing (with no need to manually configure `react-router-dom`), and API endpoints for backend features.


## Source code

You can find the source code of this project [here](https://github.com/Mr-Malomz/mini_drive).

# Prerequisites

The next steps in this post require JavaScript and React.js experience. Experience with Next.js isn’t a requirement, but it’s nice to have.

We also need the following:

- A [Cloudinary account](https://cloudinary.com/) for file hosting. [**Signup**](https://cloudinary.com/users/register/free) **is completely free**.
- An [Auth0 account](https://auth0.com/) for authentication. [**Signup**](https://auth0.com/signup?place=header&type=button&text=sign%20up) **is completely free**.

## Getting Started

We need to create a Next.js starter project by navigating to the desired directory and running the command below in our terminal


    npx create-next-app mini_drive && cd mini_drive

This command creates a Next.js project called `mini_drive` and navigates into the project directory.

We proceed to install the required dependencies with:


    npm install react-helmet cloudinary-react @auth0/nextjs-auth0 axios

`react-helmet` is a [library](https://github.com/nfl/react-helmet) that takes plain HTML tags and outputs plain HTML tags. It will help us avoid the `Reference error: Window is not Defined` error when using client-side only packages in Next.js.

`cloudinary-react` is a [library](https://github.com/cloudinary/cloudinary-react#readme) that handles rendering of media files.

`@auth0/nextjs-auth0` is a [library](https://github.com/auth0/nextjs-auth0) for implementing user authentication in Next.js applications.

`axios` is a [library](https://github.com/axios/axios) that handles http calls.

# File storage and management with Cloudinary

With the project dependencies installed, we can leverage Next.js [CSS Module](https://github.com/css-modules/css-modules) support to style our page by replacing the content in `Home.module.css` in the `styles` folder with the gist below:


{% gist https://gist.github.com/Mr-Malomz/de79cf4e75c1122b50fba8be0bdda17e %}

With that done, we can update  `index.js` file in the `pages`  folder by importing the styles created and configure Cloudinary Upload Widget to upload files to Cloudinary. We also include state variables to handle: 

- Changing tabs between images, videos and files
- The filetype to be uploaded(images, videos or files). We will use this to tag our upload
- The returned url.

```javascript
    import { Helmet } from 'react-helmet';
    import styles from '../styles/Home.module.css';
    import { useState } from 'react';
    
    export default function Home() {
      const [tab, setTab] = useState(1);
      const [filetype, setFiletype] = useState('');
      const [url, setURL] = useState(null);
    
      const handleChange = (e) => {
        setFiletype(e.target.value);
      };
    
      const openWidget = () => {
        window.cloudinary
          .createUploadWidget(
            {
              cloudName: 'dtgbzmpca',
              uploadPreset: 'tca2j0ee',
              tags: [filetype],
            },
            (error, result) => {
              if (!error && result && result.event === 'success') {
                setURL(result.info.url);
              }
            }
          )
          .open();
      };
    
      const handleSubmit = (e) => {
        e.preventDefault();
        openWidget();
      };
    
      return (
        <div>
          <Helmet>
            <script src='https://upload-widget.cloudinary.com/global/all.js' />
          </Helmet>
          {/* remaining JSX comes here */}
        </div>
      );
    }
```

The Cloudinary Upload Widget also configures `cloudName`  `uploadPreset` and `tag`. The `cloudname` is the name of our Cloudinary account,  `uploadPreset` is a parameter that enables us to centrally define a set of asset upload options instead of specifying them in each upload call and `tags` (highlighted above) are keywords to make files more searchable. `tags` will come in handy when separating our files into tabs and updated the `url` with returned URL. Finally, we used `react-helmet` to add Cloudinary Upload Widget CDN to the application.


![Shaded portions are where your cloud name is located in Cloudinary](https://paper-attachments.dropbox.com/s_4BCCCE95ACE9B0C1081840B08C278ED838CD7BEFE9CA8C7C882D08A88A918EB2_1620114874519_cloudname.png)


To create a new upload preset, click on the **Settings** Icon, select the **Upload** tab, navigate to the **Upload presets** section, click on the **Add Upload preset**.

![select preset](https://paper-attachments.dropbox.com/s_4BCCCE95ACE9B0C1081840B08C278ED838CD7BEFE9CA8C7C882D08A88A918EB2_1620116773149_Screenshot+2021-05-04+at+01.49.21.png)




![copy preset name, change to Unsigned and save](https://paper-attachments.dropbox.com/s_4BCCCE95ACE9B0C1081840B08C278ED838CD7BEFE9CA8C7C882D08A88A918EB2_1620117224843_Screenshot+2021-05-04+at+01.50.21.png)



Find out more about Cloudinary Upload Widget [here](https://cloudinary.com/documentation/upload_widget).

Then we need to include our markup for form and tabs as shown below.


{% gist https://gist.github.com/Mr-Malomz/8406d72159c8f062749bfde29dc00368 %}

With that done, we can start a development server using the command below:


    npm run dev


![running application](https://paper-attachments.dropbox.com/s_39889F3A941E007B2BE724DE6F2C4C4FFE6C2B007CCCDB3B36F915067C40E3D1_1633426447638_Screenshot+2021-10-05+at+10.33.32.png)

## Adding components to handle images, videos and files

To display our files, we need to create a `components` folder in the root directory and, in the folder, create an `ImageList.js`,  `VideoList.js`, and `FileList.js` files and add the code snippet below respectively.

**ImageList.js**

{% gist https://gist.github.com/Mr-Malomz/d344bd18876117c40edf711266e41680 %}

**VideoList.js**

{% gist https://gist.github.com/Mr-Malomz/5cbd2101f95d5c62b28e0a058c98991e %}

**FileList.js**

{% gist https://gist.github.com/Mr-Malomz/f3a98007eb770af14bd3cb561847d202 %}

The above snippets perform these tasks respectively:

- Import the required dependencies.
- Create a component that takes `url` as a prop.
- Create states to manage loading, error and returned list.
- Use the `useEffect` hooks to get list of resources matching the specified tag and update state accordingly.
    The Cloudinary URL follows this format

   `http://res.cloudinary.com/<your_cloud_name>/<resource_type>/list/<tag>.json`


`<your_cloud_name>`  is the name of our Cloudinary account.
`<resource_type>` is the resource type. `image`, `video`, and `raw` in our case.
`<tag>` is to get the list of  resources matching the tag. **images**, **videos**, and **files** in our case
    
- Conditionally render the returned list of Images and Videos using `cloudinary-react` and list of files.

Finally, we need to import the components and include them inside the `index.js`

**Updated Index.js**

{% gist https://gist.github.com/Mr-Malomz/e36b50d4a6e76afa1a917af89b445b7c %}

## Cloudinary Restricted Media Types

In other to protect our account and quotas from being abused or misused by others. Cloudinary restricts creation of new resources via unsigned requests. Before we start testing our application, we need to disable **Resource List** in our Cloudinary console.

To do this, we need to [login](https://cloudinary.com/users/login) into our Cloudinary account, click on the **settings icon,** select the **Security** tab, and uncheck the **Resource List** option, then scroll down to the bottom of the page and **Save**

![Resource List.](https://paper-attachments.dropbox.com/s_39889F3A941E007B2BE724DE6F2C4C4FFE6C2B007CCCDB3B36F915067C40E3D1_1633442222698_Screenshot+2021-10-05+at+14.50.24.png)


Finally, we can test our application by selecting the media type, uploading files and view them on different tabs.

# Adding Authentication with Auth0
## Setup a Developer Account

To get started, we need to log into our [Auth0](https://auth0.com/auth/login) dashboard. Click on **Applications**

![Auth0 Dashboard](https://paper-attachments.dropbox.com/s_39889F3A941E007B2BE724DE6F2C4C4FFE6C2B007CCCDB3B36F915067C40E3D1_1633445331548_Screenshot+2021-10-05+at+15.44.55.png)


In the application page, click on the **Create Application** button, input application name `auth0Cloudinary` in our case, select **Regular Web Application** as the application type and **Create**

![Creating a new app](https://paper-attachments.dropbox.com/s_39889F3A941E007B2BE724DE6F2C4C4FFE6C2B007CCCDB3B36F915067C40E3D1_1633446208392_Screenshot+2021-10-05+at+15.50.45.png)


Click on the **Settings** tab and copy the **Domain,** **Client ID,** and **Client Secret**.

![copy credentials](https://paper-attachments.dropbox.com/s_39889F3A941E007B2BE724DE6F2C4C4FFE6C2B007CCCDB3B36F915067C40E3D1_1633446417723_Screenshot+2021-10-05+at+16.04.21.png)


Then scroll down to the **Applications URIs** section and fill in the details below for **Allowed Callback URLs** and **Allowed Logout URLs,** respectively.

- Allowed Callback URLs
    - `http://localhost:3000/api/auth/callback`
- Allowed Logout URLs
    - `http://localhost:3000/`
![URIs](https://paper-attachments.dropbox.com/s_39889F3A941E007B2BE724DE6F2C4C4FFE6C2B007CCCDB3B36F915067C40E3D1_1633447038602_Screenshot+2021-10-05+at+16.12.18.png)


Then scroll down to the bottom of the page and click on the **Save Changes** button**.**

## Integrating Auth0 with Next.js

To integrate Auth0 in our application, we need to create an `.env.local` file in our root directory and fill in the required parameters(**Domain, Client ID and Client Secret**) as shown below:

    AUTH0_SECRET='LONG_RANDOM_VALUE'
    AUTH0_BASE_URL='http://localhost:3000'
    AUTH0_ISSUER_BASE_URL='https://YOUR_AUTH0_DOMAIN.auth0.com'
    AUTH0_CLIENT_ID='YOUR_AUTH0_CLIENT_ID'
    AUTH0_CLIENT_SECRET='YOUR_AUTH0_CLIENT_SECRET'

We can run the snippet below on our terminal to generate a random secret for the `AUTH0_SECRET`

    node -e "console.log(crypto.randomBytes(32).toString('hex'))"

Next, we need to create a dynamic route file for our APIs. We need to navigate to the `pages` folder, inside this folder, navigate to the `api` folder, in the folder, create an `auth` folder, and inside this folder, create an `[…auth0].js` file and add the snippet below:

```javascript
    import { handleAuth } from '@auth0/nextjs-auth0';
    export default handleAuth();
```

The `handleAuth()` function will generate APIs for:

- Login
    - `/api/auth/login`
- Logout
    - `/api/auth/logout`
- Fetch user data
    - `/api/auth/me`
- Redirect user on successful login.
    - `/api/auth/callback`

Next, we need to update `_app.js` file inside the `pages` folder as shown below: 

```javascript
    import { UserProvider } from '@auth0/nextjs-auth0';
    export default function App({ Component, pageProps }) {
      return (
        <UserProvider>
          <Component {...pageProps} />
        </UserProvider>
      );
    }
```

Finally, we need to update `index.js` as shown below

```javascript
    //other imports here
    import { withPageAuthRequired } from '@auth0/nextjs-auth0';
    
    export default function Home({ user }) { //update this with destructure props
      //state goes here
    
      //functions goes here
      
      return (
        <div>
          <Helmet>
            <script src='https://upload-widget.cloudinary.com/global/all.js' />
          </Helmet>
          <main className={styles.files}>
            <header className={styles.header}>
              <a href='/api/auth/logout' className={styles.logout}> {/* add href to this*/}
                Log Out
              </a>
            </header>
            <p className={styles.name}>Hi {user.name}</p> {/*add this*/}
            {/*remaining code snippet goes here*/}
          </main>
        </div>
      );
    }
    export const getServerSideProps = withPageAuthRequired({
      returnTo: '/',
    });
```

In the snippet above, we imported `withPageAuthRequired` and also modified the `log out` link. We used Next.js inbuilt method `getServerSideProps` to call the `withPageAuthRequired` method, specified the URL to redirect to after login, accessed the `user` props it returned and then displayed the user’s name.

**Complete index.js**

{% gist https://gist.github.com/Mr-Malomz/87394da413ad29eb00da709ce31d3f7c %}

We can test our application by starting the development server again, sign up, and then log in to access our application.

# Conclusion

This post discussed how to build a personalized and secure file storage system using Cloudinary, Auth0, and Next.js.

You may find these resources useful:

- [Cloudinary upload presets](https://cloudinary.com/documentation/upload_presets)
- [Cloudinary upload widget](https://cloudinary.com/documentation/upload_widget)
- [Next.js Auth0 library](https://github.com/auth0/nextjs-auth0)

Content created for the [Hackmamba](https://content.hackmamba.io/) Jamstack Content Hackathon.