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
Next go to the project dir in the terminal and install the dependencies with the following command:
cd project-dir && npm install
One last install, this time is the SDK of Auth0 for single-page applications
npm install @auth0/auth0-spa-js
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=
Step 2: Create an Auth0 project
Before drop your first lines of code you'll need to create a new auth project for that:
- Go to Auth0 and create an account
- 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.
- Select Single page web applications and give VueAuth as the application name, you can come back and change it later.
- Go to settings tab in the newly created project and copy
Domain
andClient ID
toVITE_AUTH0_DOMAIN
andVITE_AUTH0_CLIENT_ID
respectively in.env.local
file - 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
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,
});
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
};
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
}
}
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;
}
...
}
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 '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();
});
}
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();
};
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,
});
}
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,
}
}
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>
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>
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>
Final result
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.