DEV Community

Anna Pobletts for Passage

Posted on • Updated on

Create Biometric-Powered Login Pages in Vue.js

In this article, you will learn how to build a simple Vue 3 application and add biometric authentication using Passage.

Users will log in to your application using the biometrics built into their devices (e.g. Face ID, Windows Hello, etc) or with magic links sent to their email. This app is built such that it only allows authenticated users to view a simple dashboard and blocks unauthenticated users. This guide will walk through creating a Vue 3 app using the Vue CLI, creating basic components, and adding authentication to the application using Passage.

If you are already familiar with Vue, you can go straight to our full example application on GitHub or skip to this section to learn how to integrate biometric authentication into an existing application.

Setup

To get started, first install the Vue CLI. The Vue CLI lets you get up and running quickly with pre-configured build settings.

npm install -g @vue/cli
Enter fullscreen mode Exit fullscreen mode

Then create a new application using the Vue CLI. The tool will provide you with options to manually select versions and features you want. For this tutorial, use the "Manually select features" option and select the "Router" feature. Make sure to select Vue 3. You can just hit enter through the remaining features.

vue create example-app 
cd example-app
Enter fullscreen mode Exit fullscreen mode

You can test your app by running the following command and then visiting http://localhost:8080.

npm run serve
Enter fullscreen mode Exit fullscreen mode

You can leave this up and running throughout the tutorial to see your progress.

Build Components for App

Set up routes for Home and Dashboard pages

Our application will have two pages, a home page with a login screen and a dashboard page that is authenticated. First, create the directory structure and routes for those pages. Create the following directories and files to set up for the router and our two main components.

cd src/
mkdir views
touch views/Dashboard.vue
touch views/Home.vue
Enter fullscreen mode Exit fullscreen mode

Now let's start filling out these files. Copy the following code into the router/index.js file to replace the default router.

import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import Dashboard from '../views/Dashboard.vue'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/dashboard',
    name: 'Dashboard',
    component: Dashboard
  }
]

const router = createRouter({
  history: createWebHistory(),
  mode: 'history',
  routes
})

export default router
Enter fullscreen mode Exit fullscreen mode

Create a Banner component

Create a banner component that will be used on both the home and dashboard pages. Copy the following code to components/Banner.vue.

<template>
  <div class="mainHeader">
    <a href="https://passage.id/"><div class="passageLogo"></div></a>
    <div class="header-text">Passage + Vue.js 3 Example App</div>
    <div class="spacer"></div>
    <a class="link" href="https://passage.id/">Go to Passage</a>
  </div>
</template>
<script>
import { defineComponent } from 'vue'

export default defineComponent({
  name: 'Banner',
})
</script>
<style scoped>
.mainHeader{
  width: 100%;
  padding: 20px 30px;
  display: flex;
  align-items: center;
  background-color: #27417E;
  color: white;
}

.header-text {
  font-size: 24px;
  margin-left: 10px;
}

.passageLogo {
  background-image: url('https://storage.googleapis.com/passage-docs/passage-logo.svg');
  background-repeat: no-repeat;
  width: 60px;
  height: 60px;
  cursor: pointer;
}

.spacer {
  flex-grow: 1;
}

.link {
  margin-left: 20px;
  color: white;
  text-decoration-color: white;
}
</style>
Enter fullscreen mode Exit fullscreen mode

Replace the template and script tags in App.vue to use the router and add some simple styling.

<template>
<div>
  <Banner />
  <div class="main-container">
    <router-view/>
  </div>
  <div className="footer">
    Learn more with our 
    <a href="https://docs.passage.id">Documentation</a> and
    <a href="https://github.com/passageidentity">Github</a>.
    <br>       
  </div>
</div>
</template>
<style>
body {
  margin: 0px;
  height: 100vh;
  font-family: sans-serif;
  background-color: #E5E5E5;
}

.main-container {
  background: white;
  box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
  border-radius: 20px;
  width: 310px;
  min-height: 310px;
  margin: 30px auto;
}

.footer {
  text-align: center;
  font-size: 18px;
}
</style>
Enter fullscreen mode Exit fullscreen mode

and add the router and banner to main.js.

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import Banner from './components/Banner.vue'

createApp(App)
  .use(router)
  .component('Banner', Banner)
  .mount('#app')
Enter fullscreen mode Exit fullscreen mode

This means that once the components are created, the home page will be accessible at http://localhost:8080/ and the dashboard will be at http://localhost:8080/dashboard.

Build Home Component

Add the following code to views/Home.vue to create the home page.

<script>
import { defineComponent } from 'vue'

export default defineComponent({
  name: 'Home',
})
</script>
Enter fullscreen mode Exit fullscreen mode

Build Dashboard Component

Add the following code to views/Dashboard.vue to create the simple dashboard page.

<template>
  <div class="dashboard">
        <div class="title">Welcome!</div>
  </div>
</template>

<script>
import { defineComponent } from 'vue'

export default defineComponent({
  name: 'Dashboard',
})
</script>

<style scoped>
.dashboard{
  padding: 30px 30px 20px;
}

.title {
  font-size: 24px;
  font-weight: 700;
  margin-bottom: 30px;
}

.message {
  overflow-wrap: anywhere;
}

.link {
  color: black;
  text-decoration-color: black;
}
</style>
Enter fullscreen mode Exit fullscreen mode

Add Authentication with Passage

Now it is time to add authentication to our application using Passage! First, install Passage from the root directory of your example app.

npm install --save @passageidentity/passage-auth
Enter fullscreen mode Exit fullscreen mode

Then import the package in the module where you intend to use the custom element, in this case the Home.vue view.

import '@passageidentity/passage-auth'
Enter fullscreen mode Exit fullscreen mode

Importing this script will register the Passage custom element for use in your Vue components. For more information about custom elements refer to the online documentation.

Create an application in the Passage Console with the following settings:

  • Authentication Origin: http://localhost:8080
  • Redirect URL: /dashboard

Create app page in Passage Console.

Once you've created your application, copy your Application ID out the console and into a .env file in the root of your example repository.

# .env
VUE_APP_PASSAGE_APP_ID=
Enter fullscreen mode Exit fullscreen mode

In the Home component, import Passage and add the custom element to the template. ‍

<template>
  <passage-auth :app-id="appId"></passage-auth>
</template>

<script>
import { defineComponent } from 'vue'
import '@passageidentity/passage-auth'

export default defineComponent({
  name: 'Home',
  setup() {
    const appId = process.env.VUE_APP_PASSAGE_APP_ID
    return {
      appId,
    }
  },
})
</script>
Enter fullscreen mode Exit fullscreen mode

Your application now has a full login and register experience!

Sample app with Passage login.

You might notice a warning in the console about the custom element. Vue works with custom elements out of the box, but by default it will log a warning to the console that it could not resolve the component for the custom element. To configure Vue with information that the <passage-auth> tag is a custom element and suppress this warning, you need to add this configuration to your vue.config.js file. Create this file at the top-level directory of your repository.


module.exports = {
  publicPath: '/',
  chainWebpack: (config) => {
    config.module
      .rule('vue')
      .use('vue-loader')
      .tap((options) => ({
        ...options,
        compilerOptions: {
          // treat any tag that starts with passage- as custom elements
          isCustomElement: (tag) => tag.startsWith('passage-'),
        },
      }))
  },
}
Enter fullscreen mode Exit fullscreen mode

Once you’ve added this, you will need to restart the server for the changes to take effect.

Verifying User Authentication

Last but not least, the application needs to prevent unauthenticated users from accessing the dashboard page. You will set up protections that will show an error message to unauthenticated users trying to access the dashboard, but that does not prevent them from reading any data that might be on the dashboard, since it is all stored in the JavaScript files.

For simplicity, there is not a backend server in this example. A simple authentication check using the PassageUser class will be implemented to protect the dashboard page from unauthorized access.

💡 Please keep in mind that this dashboard protection will not protect sensitive API endpoints. Your server should always use one of the Passage backend libraries to authorize users before returning sensitive data.

This check is implemented by creating a composable to check the authentication status of the current user using Passage. Create a file called useAuthStatus.js in the composables directory.

mkdir composables/
touch composables/useAuthStatus.js
Enter fullscreen mode Exit fullscreen mode

Copy the following code into that file. This code uses Passage to check if the current user is authenticated.

import { ref } from 'vue'
import { PassageUser } from '@passageidentity/passage-auth/passage-user'

export function useAuthStatus(){
  const isLoading = ref(true)
  const isAuthorized = ref(false)
  const username = ref('')

  new PassageUser().userInfo().then(userInfo => {
      if(userInfo === undefined){
          isLoading.value = false
          return
      }
      username.value = userInfo.email ? userInfo.email : userInfo.phone
      isAuthorized.value = true
      isLoading.value = false
  })

  return {
    isLoading,
    isAuthorized,
    username,
  }
}
Enter fullscreen mode Exit fullscreen mode

Next, incorporate this check into the Dashboard component, since authentication is required before showing the dashboard. The dashboard will show two different messages based on the result of the authentication check. The final Dashboard.vue will look like this.

<template>
  <div class="dashboard">
    <div v-if="isLoading"/>
      <div v-else-if="isAuthorized">
        <div class="title">Welcome!</div>
        <div class="message">
          You successfully signed in with Passage.
          <br/><br/>
          Your Passage User ID is: <b>{{username}}</b>
        </div>
      </div>
    <div v-else>
      <div class="title">Unauthorized</div>
      <div class="message">
        You have not logged in and cannot view the dashboard.
        <br/><br/>
        <a href="/" class="link">Login to continue.</a>
      </div>
    </div>
  </div>
</template>

<script>
import { defineComponent } from 'vue'
import { useAuthStatus } from '../composables/useAuthStatus'

export default defineComponent({
  name: 'Dashboard',
  setup() {
    const {isLoading, isAuthorized, username} = useAuthStatus()
    return {
      isLoading,
      isAuthorized,
      username,
    }
  },
})
</script>

<style scoped>
.dashboard{
  padding: 30px 30px 20px;
}

.title {
  font-size: 24px;
  font-weight: 700;
  margin-bottom: 30px;
}

.message {
  overflow-wrap: anywhere;
}

.link {
  color: black;
  text-decoration-color: black;
}
</style>
Enter fullscreen mode Exit fullscreen mode

Unauthenticated users who try to visit /dashboard will be shown an "Unauthorized" message, while authorized users will see the dashboard that includes their Passage User ID.

Conclusion

Now you can try out the biometrics authentication in the application you just built! Your application should look something like this and you can see the login experience as your users would.

Example login screens for users.

To recap, you have just:

  • created an application with Vue.js
  • added biometric authentication to your app with Passage
  • learned how to verify the authentication status of your users with Passage

Keep an eye out for part 2 of this post, where we show you how to use Passage to protect your backend API endpoints in a Vue.js + Express.js web application!

To learn more about Passage and biometric authentication for web applications, you can:

  • Explore our dashboard to view and create users, customize your application, and add friends
  • Read our guides for other tech stacks and learn how to authorize requests in your backend server
  • Join our Discord and say hi

‍Passage is in beta and actively seeking feedback on the product. If you have feedback, bug reports, or feature requests, we would love to hear from you. You can email me at anna@passage.id or fill out this form.


This article was originally published on the Passage blog.

Discussion (0)