DEV Community

Cover image for Build an authentication flow with Auth0 and Vue3
Jesus Guerrero
Jesus Guerrero

Posted on • Updated on

Build an authentication flow with Auth0 and Vue3

Introduction

In every project, you'll probably start building the login, registration, reset password functionality, well, Auth0 provides a set of tools that are going to help you to complete that kind of task faster than the traditional way.

In this guide, you'll create an entire authentication flow with Auth0 and Vue3.

By the end of this post, you'll have a vue app that allows users to register, login
and logout and be able to use that knowledge to build your next project.

Prerequisites

  • Node.js installed in your machine at least version 12.20
  • Knowledge of CSS.
  • Previous experience with Vue.
  • Basic understanding of the composition API

Step 1: Creating a new vue3 project

To create a new Vue3 project we use vite (pronounced 'vit') which is going to scaffold the structure with the latest version of vue and set the dependencies and provide a fast developer experience.

Running the following code in your terminal will ask you for the name of the new project.

npm init vite@latest --template vue
Enter fullscreen mode Exit fullscreen mode

Next go to the project dir in the terminal and install the dependencies with the following command:

cd project-dir && npm install
Enter fullscreen mode Exit fullscreen mode

One last install, this time is the SDK of Auth0 for single-page applications

npm install @auth0/auth0-spa-js
Enter fullscreen mode Exit fullscreen mode

Create a new file .env.local and type VITE_AUTH0_DOMAIN and VITE_AUTH0_DOMAIN let it there and you'll be back later to this file to place those values from Auth0.

VITE_AUTH0_DOMAIN=
VITE_AUTH0_CLIENT_ID=
Enter fullscreen mode Exit fullscreen mode

Step 2: Create an Auth0 project

Before drop your first lines of code you'll need to create a new auth project for that:

  1. Go to Auth0 and create an account
  2. In the left side menu click on Applications dropdown then Applications option and then Create Application. This will open a modal to type the name and select an application type. Application Setup
  3. Select Single page web applications and give VueAuth as the application name, you can come back and change it later.
  4. Go to settings tab in the newly created project and copy Domain and Client ID to VITE_AUTH0_DOMAIN and VITE_AUTH0_CLIENT_ID respectively in .env.local file image.png
  5. Go down a little more until the Application URIs section and you need to set some routes to let Auth0 know where to go after certain events in this case our URL is http://localhost:3000 and you need to type in Allowed Callback URLs and Allowed Logout URLs and Allowed Web Origins like is showed in the picture below

image.png

Step 3: Creating useAuth0 composable.

It's time to drop some lines of code, as Vue3 gives us the power of reactivity even outside a component you are going to use that to wrap the Auth0 flow into its own file
create a new folder in /src called utils/ and inside create a new file named useAuth0.js

In /src/utils/useAuth0.js you need to create a new reactive object to save the AuthState and it will be exported.

// utils/useAuth0.js
import createAuth0Client from '@auth0/auth0-spa-js';
import { reactive } from 'vue';

export const AuthState = reactive({
    user: null,
    loading: false,
    isAuthenticated: false,
    auth0: null,
});
Enter fullscreen mode Exit fullscreen mode

Next, in order to simplify the configuration management lets add a config constant and set the domain and client_id from our .env.local those are available using the keywords import.meta.env.NAME_OF_VARIABLE as follows:

// utils/useAuth0.js
...
const config = {
    domain: import.meta.env.VITE_AUTH0_DOMAIN,
    client_id: import.meta.env.VITE_AUTH0_CLIENT_ID
};
Enter fullscreen mode Exit fullscreen mode

Now the most important part, to wrap the authentication flow we are going to export an arrow function that takes the state as param, which will be the AuthState you made at the top of this file. And it's going to return three functions login, logout and initAuth

export const useAuth0 = (state) => { 
 // The implementation will go here

  return {
     login,
     logout,
     initAuth
  }
}
Enter fullscreen mode Exit fullscreen mode

Let's write a utility function it is not going to be returned but it will be called after login, logout, and initAuth it will be called handleStateChange and is going to pass the authentication status from Auth0 to you AuthState.

export const useAuth0 = (state) => { 
   const handleStateChange = async () => {
        state.isAuthenticated = !!(await state.auth0.isAuthenticated());
        state.user = await state.auth0.getUser();
        state.loading = false;
    }

...
}
Enter fullscreen mode Exit fullscreen mode

In the next function initAuth you'll create a new instance of Auth0Client and for that you need the configuration you saved before domain, client_id, cacheLocation and redirect_uri

  • domain **and **client_id: are the tokens you saved in .env.local
  • cacheLocation: Is where Auth0 stores the token, by default the value is 'memory' that is not going to persist after you reload the page, since we don't want this use localstarage that keeps the token even after refreshing the page.
  • redirect_uri: Remember the routes you set before in the settings of your application in Auth0, well, you need it here with window.location.origin you get the current location of the browser that will be 'http://localhost:3000' the same you saved there.

After the Auth0Client is created, call the handleStateChange function to get the current authentication state.

...
const initAuth = () => {
     state.loading = true;
     createAuth0Client({
          domain: config.domain,
          client_id: config.client_id,
          cacheLocation: 'localstorage',
          redirect_uri: window.location.origin
      }).then(async auth => {
          state.auth0 = auth;
          await handleStateChange();
      });        
}
Enter fullscreen mode Exit fullscreen mode

Next, the login, auth0 has a loginWithPopup that is going to open a popup and ask the user for the credentials to login or register after.

...
const login = async () => {
    await state.auth0.loginWithPopup();
    await handleStateChange();
};
Enter fullscreen mode Exit fullscreen mode

Next, the logout, auth0 has a logout function that accepts an object as an argument and a returnTo property is required. Here you can type your current location with window.location.origin.

...
const logout = async () => {
    state.auth0.logout({
          returnTo: window.location.origin,
    });
}
Enter fullscreen mode Exit fullscreen mode

By now your src/utils/useAuth0.js file should look like this:

// utils/useAuth0.js
import createAuth0Client from '@auth0/auth0-spa-js';
import { reactive } from 'vue';

export const AuthState = reactive({
    user: null,
    loading: false,
    isAuthenticated: false,
    auth0: null,
});

const config = {
    domain: import.meta.env.VITE_AUTH0_DOMAIN,
    client_id: import.meta.env.VITE_AUTH0_CLIENT_ID
};

export const useAuth0 = (state) => {
    const handleStateChange = async () => {
        state.isAuthenticated = !!(await state.auth0.isAuthenticated());
        state.user = await state.auth0.getUser();
        state.loading = false;
    }

    const initAuth = () => {
        state.loading = true;
        createAuth0Client({
            domain: config.domain,
            client_id: config.client_id,
            cacheLocation: 'localstorage',
            redirect_uri: window.location.origin
        }).then(async auth => {
            state.auth0 = auth;
            await handleStateChange();
        });        
    }

    const login = async () => {
        await state.auth0.loginWithPopup();
        await handleStateChange();
    };

    const logout = async () => {
        state.auth0.logout({
            returnTo: window.location.origin,
        });
    }

    return {
        login,
        logout,
        initAuth,
    }

}
Enter fullscreen mode Exit fullscreen mode

Step 4: Setup App.vue

Let's modify the src/App.vue.

Take a look at the final code of the App.vue I'll explain bellow.

<script setup>
import { useAuth0, AuthState } from "./utils/useAuth0";
const { login, logout, initAuth } = useAuth0(AuthState);

initAuth();
</script>

<template>
  <div v-if="!AuthState.loading">
    <img alt="Vue logo" src="./assets/logo.png" />
    <div v-if="!AuthState.isAuthenticated">
      <button @click="login()" class="btn btn-primary">Login</button>
    </div>

    <div v-else>
      <p> Welcome to VueAuth <strong>{{ AuthState.user.name }}</strong></p>
      <button @click="logout()" class="btn btn-secondary">Logout</button>
    </div>
  </div>

  <div v-else>
    Loading ...
  </div>
</template>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}

.btn {
  padding: 8px 12px;
  margin-bottom: 0;
  font-size: 14px;
  font-weight: 400;
  line-height: 1.5;
  border: none;
  cursor: pointer;
  min-width: 100px;
  border-radius: 4px;
  font-weight: bold;
}

.btn-primary {
  background: #41B883;
  color: white;
}

.btn-secondary {
  background: #aaa;
  color: white;
}
</style>
Enter fullscreen mode Exit fullscreen mode

At the top of the file in the script section AuthState and useAuth0 are imported from the wrapper you created.

The AuthState is used to call useAuth0 and get the login, logout and initAuth functions.

And at the end of the script initAuth() is called to create the instance and get the current authentication state of the user.

<script setup>
import { useAuth0, AuthState } from "./utils/useAuth0";
const { login, logout, initAuth } = useAuth0(AuthState);

initAuth();
</script>
Enter fullscreen mode Exit fullscreen mode

In the template section we check if the app is loading and if the user is not authenticated show the login button that calls the login function in the script but if it is authenticated show the user name and a logout button calling the logout function from the script.

If the app is loading it shows the loading... text.

<template>
  <div v-if="!AuthState.loading">
    <img alt="Vue logo" src="./assets/logo.png" />
    <div v-if="!AuthState.isAuthenticated">
      <button @click="login()" class="btn btn-primary">Login</button>
    </div>

    <div v-else>
      <p> Welcome to VueAuth <strong>{{ AuthState.user.name }}</strong></p>
      <button @click="logout()" class="btn btn-secondary">Logout</button>
    </div>
  </div>

  <div v-else>
    Loading ...
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Final result


VueAuth - Watch Video





Conclusion

You built an authentication flow with Vue3 and Auth0, congrats! Now that you are familiar with Auth0 and its benefits you are able to implement it in your next project.

Thanks for reading. If you have any questions the comments are open, or if you like Twitter as well as my Github where I do some experiments and projects.

Have a good day.

Resources

Discussion (0)