Creating a personal image gallery with VueJS, Auth0 and Cloudinary.

Lere🦄💜 - Oct 17 '21 - - Dev Community

In this article, you will be learning how to create a personal image gallery using VueJS, Auth0 and Cloudinary API. With this application, users will be able to sign in using the authorization and authentication features of Auth0, create a profile and upload and download images with the help of Cloudinary.

You might be looking for the complete code. I've created a GitHub repo where you can find the complete code for the app.

Setting up Auth0

Auth0 is a flexible, drop-in solution to add authentication and authorization services to your applications. You are able to avoid the cost, time, and risk that come with building your own solution to authenticate and authorize users.

To set up Auth0, the first thing you will be needing is an Application in Auth0, so head on over to their website and create an account.

After setting up your account, the next thing you want to do is create your first application. You can do that by clicking the applications item on the menu on the left side and click “Create Application”.

Image1

From the image above, we see what that dropdown looks like;

  1. The first arrow points to the name box; here you can enter the name for your app and in this example, we’ll be calling it “My App”.
  2. The second arrow points to the application type options available, and for this project we’ll be using the “Single Page Web Applications” option, because just like the description says, we’ll be building a JavaScript front-end app that uses an API.
  3. When that’s done, click “Create”.

This sets up the application with default configuration for a Single Page Web Application. This configuration can be tweaked or changed later if you need or want to.

In the settings, add http://localhost:3000 to the Allowed Callback URLs, Allowed Logout URLs, Allowed Web Origins and Allowed Origins (CORS) fields. This is due to the fact that the sample client will be running on http://localhost:3000 you'll need to add the appropriate values if you're running on a server or a different port or on a host that isn't localhost

The rest of the defaults can be left as is.

From the image above, we see what that dropdown looks like;

  1. The first arrow points to the name box; here you can enter the name for your app and in this example, we’ll be calling it “My App”.
  2. The second arrow points to the application type options available, and for this project we’ll be using the “Single Page Web Applications” option, because just like the description says, we’ll be building a JavaScript front-end app that uses an API.
  3. When that’s done, click “Create”.

This sets up the application with default configuration for a Single Page Web Application. This configuration can be tweaked or changed later if you need or want to.

In the settings, add http://localhost:3000 to the Allowed Callback URLs, Allowed Logout URLs, Allowed Web Origins and Allowed Origins (CORS) fields. This is due to the fact that the sample client will be running on http://localhost:3000 you'll need to add the appropriate values if you're running on a server or a different port or on a host that isn't localhost

The rest of the defaults can be left as is.

Setting up Cloudinary

Cloudinary is a media management platform for web and mobile developers. Cloudinary is an end-to-end image- and video-management solution for websites and mobile apps, covering everything from image and video uploads, storage, manipulations, optimizations to delivery.

First, just like we did with Auth0, we have to create an account on Cloudinary.

Image2

When creating an account on Cloudinary, it’s important to choose the option that best describes your use for it. Here, we’ll be making use of their API to manipulate media on our project, so it’s only appropriate that we select the option “Programmable Media for image and video API”. When that’s over and done, you can then proceed to creating your account.

Image11

When your account is created and fully functional, you should see your dashboard, just like the one above.

Log into your Cloudinary account and set up an upload preset that will be handling the images being uploaded. Before setting up an upload preset, you’ll first have to create a media library folder that will hold the images being uploaded. You can do this using the following steps;

  1. Go to the media library.

Image3

  1. Create a new folder. we’ll be calling our folder “My images”.

Image4

Now, Cloudinary gives developers a tool that allows us integrate it in VueJS applications, called the VueJS SDK. The Cloudinary Vue.js SDK serves as a layer on top of Cloudinary's JavaScript (Cloudinary-core) library.

We can install the SDK by running the following command:

npm install cloudinary-vue

Enter fullscreen mode Exit fullscreen mode

Then we can proceed with creating the upload preset.
Go to the Settings option and click Upload (tab)

Image5

Scroll down to and click on add upload preset.

Image6

Set the Upload preset name, select the Unsigned signing mode and input the name of the folder that you created above. In this case, “my images”

Image7

Set the Upload preset name, select the Unsigned signing mode and input the name of the folder that you created above. In this case, “my images”

img8

It’s important to note that Unsigned upload presets are used when implementing upload capabilities from client-side apps i.e. allowing uploads straight from the browser.

Go to Upload control and switch the return delete token on if you’d like to be able to delete uploaded images.

Image9

Note: You can only use the delete token to delete an uploaded file within a span of ten minutes after uploading it.

Set up the rest of the settings to your preferences and click on 'save' to save this new upload preset.
Back on the settings page and upload tab the new upload preset will be listed amongst the existing presets.

Image10

Note: You can only use the delete token to delete an uploaded file within a span of ten minutes after uploading it.

Set up the rest of the settings to your preferences and click on 'save' to save this new upload preset.
Back on the settings page and upload tab the new upload preset will be listed amongst the existing presets.

Credit: James Sinkala

The Vue Component

So to create the Vue Component that will allow users upload images to Cloudinary through the created upload preset.

The Template

The template below contains the file upload button that will trigger the uploader modal. We can put in a Main.vue file.


<template>
<div>
    <Header/>
  <v-card>
    <div class="helldo">
      <input type="file" @change="onFileSelected"> 
      <v-btn v-if="displayUploadBtn" outlined @click="uploadFile"> Upload </v-btn >
    </div>
    <v-tabs
      color="deep-purple accent-4"
      right
    >
      <v-tab>Landscape</v-tab>
      <v-tab>City</v-tab>
      <v-tab>Abstract</v-tab>
      <v-tab-item
        v-for="n in 3"
        :key="n"
      >
        <v-container fluid>
          <v-row>
            <v-col
              v-for="i in 6"
              :key="i"
              cols="12"
              md="4"
            >
              <v-img
                :src="`https://picsum.photos/500/300?image=${i * n * 5 + 10}`"
                :lazy-src="`https://picsum.photos/10/6?image=${i * n * 5 + 10}`"
                aspect-ratio="1"
              ></v-img>
            </v-col>
          </v-row>
        </v-container>
      </v-tab-item>
    </v-tabs>
  </v-card>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

The :src attributes on line 35 and 36 serves to generate random images from Picsum.

Installing Axios

To make promise-based HTTP requests, I’d be installing Axios.

#install axios
npm i axios
Enter fullscreen mode Exit fullscreen mode

After installing Axios, I can then use it to make request to the Cloudinary API.

<script>
import axios from "axios"
import Header from '../Header/Main.vue'

export default {
    name: "home",
    data: () => ({
    displayUploadBtn: false,
    selectedFile: null,
    CLOUDINARY_URL: "https://api.cloudinary.com/v1_1/yungscript/upload",
    CLOUDINARY_PRESET: "hk7esqdc" //your Cloudinary preset
}),

components: {
  Header
  },
    methods: {
    onFileSelected(event) {
    this.selectedFile = event.target.files[0]
    this.displayUploadBtn = true
},

uploadFile() {

let file = this.selectedFile
let formData = new FormData()

console.log(file);

formData.append("file", file)
formData.append("upload_preset", this.CLOUDINARY_PRESET)

axios({
  url: this.CLOUDINARY_URL,
  method: "POST",
  headers: {
  "Content-Type": "application/x-www-form-urlencoded"
},
data: formData
})
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
})
}
}
};
</script>
Enter fullscreen mode Exit fullscreen mode

Now the CLOUDINARY_URL is the URL that contains the images uploaded by the user.

Integrating Auth0

From the account you created above, you’d be given a domain name and a client ID. Your domain is the base URL that you will use to access the Auth0 APIs and the URL where you'll redirect users to log in, while the client ID is an alphanumeric string, and it's the unique identifier for your application. You cannot modify the Client ID. You will use the Client ID to identify the Auth0 Application to which the Auth0 SPA SDK needs to connect.

Now, you’ll create an auth_config.json file and add both your client ID and Domain.


{
    "domain": "YOUR_AUTH0_DOMAIN",
  "clientId": "YOUR_AUTH0_CLIENT_ID"
}
Enter fullscreen mode Exit fullscreen mode

Now, Execute the following command to install the Auth0 SPA SDK:

npm install @auth0/auth0-spa-js

Enter fullscreen mode Exit fullscreen mode

Create an auth directory within the src directory:

mkdir src/auth
Enter fullscreen mode Exit fullscreen mode

Create an index.js file within the src/auth directory:

touch src/auth/index.js
Enter fullscreen mode Exit fullscreen mode

Populate index.js with the following code:


/**
 *  External Modules
 */

import Vue from 'vue';
import createAuth0Client from '@auth0/auth0-spa-js';

/**
 *  Vue.js Instance Definition
 */

let instance;

export const getInstance = () => instance;

/**
 *  Vue.js Instance Initialization
 */

export const useAuth0 = ({
  onRedirectCallback = () =>
    window.history.replaceState({}, document.title, window.location.pathname),
  redirectUri = window.location.origin,
  ...pluginOptions
}) => {
  if (instance) return instance;

  instance = new Vue({
    data() {
      return {
        auth0Client: null,
        isLoading: true,
        isAuthenticated: false,
        user: {},
        error: null,
      };
    },
    methods: {

Enter fullscreen mode Exit fullscreen mode

Now to handle the callback when logging in using a redirect.


/** Handles the callback when logging in using a redirect */
      async handleRedirectCallback() {
        this.isLoading = true;
        try {
          await this.auth0Client.handleRedirectCallback();
          this.user = await this.auth0Client.getUser();
          this.isAuthenticated = true;
        } catch (error) {
          this.error = error;
        } finally {
          this.isLoading = false;
        }
      },

Enter fullscreen mode Exit fullscreen mode

In cases where the user forget the password or token and might have to retrieve or renew it, we can then use the following to allow for token retrieval and redirect the user back to the login page.


/** Authenticates the user using the redirect method */
      loginWithRedirect(options) {
        return this.auth0Client.loginWithRedirect(options);
      },
/** Returns the access token. If the token is invalid or missing, a new one is retrieved */
      logout(options) {
        return this.auth0Client.logout(options);
      },
/** Returns the access token. If the token is invalid or missing, a new one is retrieved */
      getTokenSilently(o) {
        return this.auth0Client.getTokenSilently(o);
      },
    },

/** Use this lifecycle method to instantiate the SDK client */
    async created() {
      this.auth0Client = await createAuth0Client({
        ...pluginOptions,
        domain: pluginOptions.domain,
        client_id: pluginOptions.clientId,
        audience: pluginOptions.audience,
        redirect_uri: redirectUri,
      });

      try {
// If the user is returning to the app after authentication..
        if (
          window.location.search.includes('code=') &&
          window.location.search.includes('state=')
        ) {
          const { appState } = await this.auth0Client.handleRedirectCallback();

 // Notify subscribers that the redirect callback has happened, passing the appState
                    // (useful for retrieving any pre-authentication state)
          onRedirectCallback(appState);
        }
      } catch (error) {
        this.error = error;
      } finally {
 // Initialize our internal authentication state
        this.isAuthenticated = await this.auth0Client.isAuthenticated();
        this.user = await this.auth0Client.getUser();
        this.isLoading = false;
      }
    },
  });

  return instance;
};

/**
 *  Vue.js Plugin Definition
 */
// Create a simple Vue plugin to expose the wrapper object throughout the application
export const Auth0Plugin = {
  install(Vue, options) {
    Vue.prototype.$auth = useAuth0(options);
  },
};
Enter fullscreen mode Exit fullscreen mode

Routing

The following block of code serves to tell the browser what page to load. From the four components created, I can import and reference them as below;


import Vue from 'vue'
import VueRouter from 'vue-router'
// import Home from '../views/Home.vue'
// import Profile from "../components/Profile/Profile.vue";
const Home = () => import(/* webpackChunkName: "about" */ '../components/Home/Main.vue') //imports the Home Component
const About = () => import(/* webpackChunkName: "about" */ '../components/About/Main.vue') //imports the About Component
const Profile = () => import(/* webpackChunkName: "about" */ '../components/Profile/Main.vue') //imports the Profile Component
const Media = () => import(/* webpackChunkName: "about" */ '../components/Media/Main.vue') //imports the Media component
import { authGuard } from "../auth/authGuard";

Vue.use(VueRouter)
const routes = [
  {
    path: '/',
    name: 'Home',
    // redirect: "/home",
    component: Home
  },
  {
    path: '/about',
    name: 'About', //go to About Page
    component: About
  },
  {
    path: "/profile",
    name: "Profile", // go to Profile Page
    component: Profile,
    beforeEnter: authGuard //before user goes to profile page, it checks if user is logged in. If not, it redirects the user to the login page.
  },
  {
    path: "/media",
    name: "Media", // go to Profile Page
    component: Media
  }
]
const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})
export default router

Enter fullscreen mode Exit fullscreen mode

Plugins

I added the Vuetify package to style the project but feel free to use what you like. Bootstrap works just fine as well. Install Vuetify in your Vue app after creating your Vue project using Vue CLI like this;

vue add vuetify
Enter fullscreen mode Exit fullscreen mode

Import Vue into your project as such:


import Vue from 'vue';
import Vuetify from 'vuetify/lib/framework';
Vue.use(Vuetify);
export default new Vuetify({
});
Enter fullscreen mode Exit fullscreen mode

Voila, you’ve successfully integrated Cloudinary and Auth0 into your VueJS project.

Content created for the Hackmamba Jamstack Content Hackathon with Auth0 and Cloudinary.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .