Vue.js Tutorial
This tutorial demonstrates how to add user login, logout, and profile to a Vue 3 application.
This tutorial shows how to use PlusAuth with Vue 3 Single Page Application. If you do not have a PlusAuth account, register from here.
Create PlusAuth Client
After you sign up or log in to PlusAuth, you need to create a client to get the necessary configuration keys in the dashboard.
Go to Clients and create a client with the type of Single Page Application
Configure Client
Get Client Properties
You will need your Client Id
for interacting with PlusAuth. You can retrieve it from the created client's details.
Configure Redirect and Logout URIs
When PlusAuth authenticates a user, it needs a URI to redirect back with access and id token. That URI must be in your client's Redirect URI
list. If your application uses a redirect URI which is not white-listed in your PlusAuth Client, you will receive an error.
The same thing applies to the logout URIs. After the user logs out, you need a URI to be redirected.
http://localhost:5173/callback
and http://localhost:5173/silent-renew.html
.
The Logout URL you need to add to the Post Logout Redirect URIs field is http://localhost:5173/
.Create a Vue Application
Follow Quick Start Guide from Vue.js official document site and create a project.
Make sure you have answered to Add Vue Router for Single Page Application development?
prompt as Yes
.
Other prompts are not required for this tutorial.
Here is the overview of the commands:
Scaffolding project in ./
# Install the CLI
npm create vue@latest
✔ Project name: … <your-project-name>
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit testing? … No / Yes
✔ Add an End-to-End Testing Solution? … No / Cypress / Playwright
✔ Add ESLint for code quality? … No / Yes
✔ Add Prettier for code formatting? … No / Yes
Scaffolding project in ./<your-project-name>...
Done.
Install OIDC Client
For interacting with PlusAuth it is advised to use an OpenID Connect library. In this tutorial we will be using oidc-client-js but you could use any OpenID Connect library.
Install oidc-client-js
with the following command
npm install @plusauth/oidc-client-js
Configure Vue Application to use PlusAuth
We will be using dotenv
files for maintaining providing some constant values. Vite supports them,
so no need for extra configuration.
Create the .env file
Create the .env
file at the root of your project with the following and modify values accordingly.
# .env
VITE_OIDC_ISSUER=https://<YOUR_PLUSAUTH_TENANT_NAME>.plusauth.com/
VITE_OIDC_CLIENT_ID=<YOUR_PLUSAUTH_CLIENT_ID>
.env.example
to .env
and replace the values accordingly.Configure OIDC Client
We need to initialize our OIDC Client library to handle authentication-related operations.
Create auth.js
in src
folder. Configure oidc-client-js
as following:
import { OIDCClient } from '@plusauth/oidc-client-js'
const Auth = new OIDCClient({
issuer: import.meta.env.VITE_OIDC_ISSUER,
client_id: import.meta.env.VITE_CLIENT_ID,
redirect_uri: 'http://localhost:5173/callback',
silent_redirect_uri: 'http://localhost:5173/silent-renew',
post_logout_redirect_uri: 'http://localhost:5173/',
response_mode: 'fragment',
response_type: 'id_token token',
scope: 'openid profile',
checkSession: true,
autoSilentRenew: true,
requestUserInfo: true,
})
export { Auth }
You may have noticed that the values defined in the Configure Client section are used here. If you have used different values make sure to update this file accordingly.
Configure Vue Application
Let's start by defining our application's entry point file.
Go to file named main.js
in the src
folder. Import the auth
file we have created above and make the following changes.
import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { Auth } from './auth'
const app = createApp(App)
app.use(router)
// Make auth object global to access from anywhere
app.config.globalProperties.$auth = Auth
Auth.initialize() .then(() => {}) .catch(console.error) .finally(() => { app.mount('#app')
})
Configure Router
Now let's define our application's router.
We are going to define routes of our views. requiresAuth
flag in the metadata of routes will ensure those routes are accessible only by authenticaed users.
Create router.js
in src
folder as following:
import * as VueRouter from 'vue-router'
import { Auth } from "./auth.js";
import Home from './components/Home.vue'
import Profile from './components/Profile.vue'
import Unauthorized from './components/Unauthorized.vue'
import AuthCallback from './components/AuthCallback.vue'
import SilentRenew from './components/SilentRenew.vue'
const routes = [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/callback', // Authentication redirect uri
name: 'AuthCallback',
component: AuthCallback
},
{
path: '/silent-renew', // Token silent renew uri
name: 'SilentRenew',
component: SilentRenew
},
{
path: '/profile',
name: 'profile',
meta: {requiresAuth: true},
component: Profile
},
{
path: '/unauthorized', // Redirect to page if user not authorized
name: 'unauthorized',
component: Unauthorized
},
{
path: '/:pathMatch(.*)*', // redirect to home if unhandled route entered
redirect: '/'
}
]
const Router = VueRouter.createRouter({
history: VueRouter.createWebHistory('/'),
routes
})
// Check user if logged in for routes that requires auth
Router.beforeEach(async (to, from, next) => {
const loggedIn = await Auth.isLoggedIn(true)
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!loggedIn) {
return next({
path: '/unauthorized'
})
}
}
return next()
})
export { Router }
Implement login, user profile, and logout
Until now, we have defined our authentication helper and routes. It is time to create the pages and interact with auth helper.
Create Main Vue Component
Let's create a simple layout for our application. Add Header
component and router-view
to App.vue
.
<template>
<div id="app">
<Header></Header>
<router-view />
</div>
</template>
<script>
import Header from './components/Header'
export default {
name: 'App',
components: { Header }
}
</script>
<style>
body {
padding-top: 5rem;
}
</style>
Create Header Component
Create Header.vue
under src/components
folder. It will be a basic header.
If a user is authenticated, it will show the user's identifier and a Logout
button.
If not, a Login
button will be there to initiate login.
<template>
<header>
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
<a class="navbar-brand" href="/">Plusauth Starter</a>
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
<ul class="navbar-nav mr-auto"></ul>
<template v-if="user">
<li class="nav-item navbar-nav">
<router-link class="nav-link" to="/profile">{{
`Logged in as: ${userDisplayName}`
}}</router-link>
</li>
<button class="btn btn-link" @click="$auth.logout()">Logout</button>
</template>
<li v-else class="nav-item navbar-nav">
<button class="btn btn-link" @click="$auth.login()">Login</button>
</li>
</div>
</nav>
</header>
</template>
<script>
export default {
data() {
return {
user: null
}
},
computed: {
userDisplayName() {
if (!this.user) {
return null
} else {
if (!this.user.given_name || !this.user.family_name) {
return this.user.username || this.user.email || this.user.sub
}
return `${this.user.given_name} ${this.user.family_name}`
}
}
},
async created() {
this.$auth.on('user_login', ({ user }) => {
this.user = user
})
this.$auth.on('user_logout', () => {
this.user = null
})
this.user = await this.$auth.getUser()
}
}
</script>
Create AuthCallback
To handle authorization results after a successful login, we need
a simple page and let the library handle the authentication result.
Create AuthCallback.vue
under src/components
folder.
<template>
<div></div>
</template>
<script>
export default {
name: 'AuthCallback',
async mounted() {
try {
await this.$auth.loginCallback(window.location.href)
this.$router.replace('/')
} catch (e) {
console.error(e)
}
}
}
</script>
Create SilentRenew
Access tokens retrieved from PlusAuth have a life span.
oidc-client-js
automatically provides access_token
renewal without too much hassle.
Before your access token expires, it will receive a new one in the background so that
your users will have a flawless app experience without signing in again.
Create SilentRenew.vue
under src/components
folder as following:
<template> </template>
<script>
import { OIDCClient } from '@plusauth/oidc-client-js'
export default {
name: 'SilentRenew',
async mounted() {
await new OIDCClient({
issuer: process.env.VITE_OIDC_ISSUER
}).loginCallback()
}
}
</script>
Create Views
HomePage
Create Home.vue
under src/components
.
<template>
<div class="jumbotron">
<div class="container">
<h1 class="display-3">Hello, world!</h1>
<p>
This is a template for a simple login/register system. It includes the
OpenID Connect Implicit Flow. To view Profile page please login.
</p>
<p>
<router-link v-if="user" class="btn btn-success btn-lg" to="/profile">
View Profile »
</router-link>
<button v-else class="btn btn-primary btn-lg" @click="$auth.login()">
Login/Register »
</button>
</p>
</div>
</div>
</template>
<script>
export default {
name: 'Home',
data() {
return {
user: null
}
},
async created() {
this.$auth.on('user_login', ({ user }) => {
this.user = user
})
this.$auth.on('user_logout', () => {
this.user = null
})
this.user = await this.$auth.getUser()
}
}
</script>
Profile Page
Create Profile.vue
under src/components
.
<template>
<div class="container" v-if="user">
<h3>Welcome {{ user.username }} !</h3>
<pre>User object: {{ JSON.stringify(user, null, 2) }} </pre>
</div>
</template>
<script>
export default {
name: 'Profile',
data() {
return {
user: null
}
},
async created() {
this.$auth.on('user_login', ({ user }) => {
this.user = user
})
this.$auth.on('user_logout', () => {
this.user = null
})
this.user = await this.$auth.getUser()
}
}
</script>
Add Unauthorized Page
We will display a page whenever a user tries to access a protected route without signing in.
Create Unauthorized.vue
under src/components
.
<template>
<div class="container">
<p>You must log in to view the page</p>
<button class="btn btn-primary" @click="$auth.login()">Log in</button>
</div>
</template>
<script>
export default {
name: 'Unauthorized'
}
</script>
See it in action
That's it. Start your app and point your browser to http://localhost:5173. Follow the Log In link to log in or sign up to your PlusAuth tenant. Upon successful login or signup, you should be redirected back to the application.