DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
  1. DZone
  2. Coding
  3. JavaScript
  4. The Lazy Developer's Guide to Authentication With Vue.js

The Lazy Developer's Guide to Authentication With Vue.js

If you're interested in learning to use Vue.js, read on to see how this developer created a PWA with Vue, and added authentication to it.

Matt Raible user avatar by
Matt Raible
·
Oct. 06, 17 · Tutorial
Like (5)
Save
Tweet
Share
10.83K Views

Join the DZone community and get the full member experience.

Join For Free

I’ll happily admit that like many of you, I’m a lazy developer. I tend to look for solutions someone else has already built before I try to build them myself. In the “old days” of the early 2000s, I used to spend a lot more time coding solutions myself. But today many solutions to coding problems are just a keyword search away. In that spirit, I approached this tutorial using keywords – looking for existing solutions first, then fitting my favorite solution on top of it. I think you’ll like the results!

When I started writing this article, I wanted to show you how to add authentication to a Vue.js app. I started learning Vue.js back in April while authoring my Front End Development for Back End Developers presentation. I bought a pro subscription to Egghead.io and watched Build Vue.js Apps with the Vue-CLI and Nuxt.js by @johnlindquist.

Vue CLI + PWA!

Every web app that has mobile users should add PWA support so the app loads faster and works offline. Vue.js has excellent PWA support. You might recognize me as a fan of PWAs if you’ve read my Ultimate Guide to Progressive Web Applications.

I installed Vue CLI and went to work.

npm install -g vue-cli
vue init pwa okta-vue-auth-example
cd okta-vue-auth-example
npm i

Vue Init

I opened the project in IntelliJ IDEA (disclaimer: my fave IDE) and was surprised to find the ESLint rules prefer ending your lines without a semicolon. The semicolon debate in JavaScript rages on!

I installed the Vue plugin for IntelliJ so *.vue files rendered with their proper syntax highlighting.

I googled a bit more and found the auth-flow example from vue-router. I started by copying some files from the auth-flow project into my project. I created a page that anyone can see (/about) and another (/dashboard) that only authenticated users can see.

Add Auth Flow Example

If you’d like to follow along, create the project using vue init as I did above, then create src/components/About.vuewith the following HTML.

<template>
  <div>
    <h2>About</h2>
  </div>
</template>

Create src/components/Dashboard.vue. This page will only be viewable to authenticated folks.

<template>
  <div>
    <h2>Dashboard</h2>
    <p>Yay you made it!</p>
  </div>
</template>

Copy the Login.vue from auth-flow to src/components/Login.vue.

<template>
  <div>
    <h2>Login</h2>
    <p v-if="$route.query.redirect">
      You need to login first.
    </p>
    <form @submit.prevent="login">
      <label><input v-model="email" placeholder="email"></label>
      <label><input v-model="pass" placeholder="password" type="password"></label> (hint: password1)<br>
      <button type="submit">login</button>
      <p v-if="error" class="error">Bad login information</p>
    </form>
  </div>
</template>

<script>
  import auth from '../auth'
  export default {
    data () {
      return {
        email: 'joe@example.com',
        pass: '',
        error: false
      }
    },
    methods: {
      login () {
        auth.login(this.email, this.pass, loggedIn => {
          if (!loggedIn) {
            this.error = true
          } else {
            this.$router.replace(this.$route.query.redirect || '/')
          }
        })
      }
    }
  }
</script>

<style>
  .error {
    color: red;
  }
</style>

This component depends on auth.js, which fakes authentication. Create src/auth.js and paste in the fake auth implementation (for now).

/* globals localStorage */

export default {
  login (email, pass, cb) {
    cb = arguments[arguments.length - 1]
    if (localStorage.token) {
      if (cb) cb(true)
      this.onChange(true)
      return
    }
    pretendRequest(email, pass, (res) => {
      if (res.authenticated) {
        localStorage.token = res.token
        if (cb) cb(true)
        this.onChange(true)
      } else {
        if (cb) cb(false)
        this.onChange(false)
      }
    })
  },

  getToken () {
    return localStorage.token
  },

  logout (cb) {
    delete localStorage.token
    if (cb) cb()
    this.onChange(false)
  },

  loggedIn () {
    return !!localStorage.token
  },

  onChange () {}
}

function pretendRequest (email, pass, cb) {
  setTimeout(() => {
    if (email === 'joe@example.com' && pass === 'password1') {
      cb({
        authenticated: true,
        token: Math.random().toString(36).substring(7)
      })
    } else {
      cb({ authenticated: false })
    }
  }, 0)
}

Copy App.vue (or the code below) from auth-flow to src/App.vue:

<template>
  <div id="app">
    <h1>Auth Flow</h1>
    <ul>
      <li>
        <router-link v-if="loggedIn" to="/logout">Log out</router-link>
        <router-link v-if="!loggedIn" to="/login">Log in</router-link>
      </li>
      <li>
        <router-link to="/about">About</router-link>
      </li>
      <li>
        <router-link to="/dashboard">Dashboard</router-link>
        (authenticated)
      </li>
    </ul>
    <template v-if="$route.matched.length">
      <router-view></router-view>
    </template>
    <template v-else>
      <p>You are logged {{ loggedIn ? 'in' : 'out' }}</p>
    </template>
  </div>
</template>

<script>
import auth from './auth'
export default {
  data () {
    return {
      loggedIn: auth.loggedIn()
    }
  },
  created () {
    auth.onChange = loggedIn => {
      this.loggedIn = loggedIn
    }
  }
}
</script>

Add the following HTML/CSS to the bottom of src/App.vue to give it the same styling that the auth-flow project has.

<style>
  html, body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
    color: #2c3e50;
  }

  #app {
    padding: 0 20px;
  }

  ul {
    line-height: 1.5em;
    padding-left: 1.5em;
  }

  a {
    color: #7f8c8d;
    text-decoration: none;
  }

  a:hover {
    color: #4fc08d;
  }
</style>

Replace src/router/index.js with the following code. This sets up routing for the application, protects the dashboard, and looks intuitive doing it!

import Vue from 'vue'
import Router from 'vue-router'
import auth from '@/auth'
import About from '@/components/About.vue'
import Dashboard from '@/components/Dashboard.vue'
import Login from '@/components/Login.vue'

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: __dirname,
  routes: [
    { path: '/about', component: About },
    { path: '/dashboard', component: Dashboard, beforeEnter: requireAuth },
    { path: '/login', component: Login },
    { path: '/logout',
      beforeEnter (to, from, next) {
        auth.logout()
        next('/')
      }
    }
  ]
})

function requireAuth (to, from, next) {
  if (!auth.loggedIn()) {
    next({
      path: '/login',
      query: { redirect: to.fullPath }
    })
  } else {
    next()
  }
}

After making these changes, you should be able to run npm start and authenticate with the hard coded values from auth.js (joe@example.com / password1).

If you open your browser to http://localhost:8080, you should see a screen that resembles the one below.

Auth Flow Home

Click on the Dashboard link, and you’ll be prompted to log in.

Auth Flow Dashboard

Entering joe@example/password1 will get you to the next step.

Auth Flow Dashboard

If you open Chrome Developer Tools, you’ll see a message that recommends installing Vue Devtools extension for a better development experience.

Add Okta for Authentication

To replace the fake, hard-coded authentication in src/auth.js, start by installing the Okta Auth SDK:

npm install @okta/okta-auth-js --save

Replace the code in auth.js with the following code that uses the Auth SDK to log in and save a session token as the token. If you don’t have an Okta Developer account, create one. Then replace {yourOktaDomain} in the code below with your information (for example, https://dev-123456.oktapreview.com).

/* globals localStorage */
const OktaAuth = require('@okta/okta-auth-js')
const authClient = new OktaAuth({url: 'https://{yourOktaDomain}.com'})

export default {
  login (email, pass, cb) {
    cb = arguments[arguments.length - 1]
    if (localStorage.token) {
      if (cb) cb(true)
      this.onChange(true)
      return
    }
    return authClient.signIn({
      username: email,
      password: pass
    }).then(response => {
      if (response.status === 'SUCCESS') {
        localStorage.token = response.token
        if (cb) cb(true)
        this.onChange(true)
      }
    }).fail(err => {
      console.error(err.message)
      if (cb) cb(false)
      this.onChange(false)
    })
  },

  getToken () {
    return localStorage.token
  },

  logout (cb) {
    delete localStorage.token
    if (cb) cb()
    this.onChange(false)
    return authClient.signOut()
  },

  loggedIn () {
    return !!localStorage.token
  },

  onChange () {
  }
}

To make it possible to talk to Okta from http://localhost:8080, you’ll need to add it as a trusted origin. You can do this by signing in to your Okta dashboard and navigating to API > Trusted Origins. Add http://localhost:8080 and select the CORS and Redirect checkboxes.

Add Origin

After making this change, everything should work the same as before, but you’ll have to use Okta credentials to log in.

Improve Security

You can improve the security of your application by turning off autocomplete in src/components/Login.vue, adding focus to the email field, and initializing its default value to being empty. Replace your current Login.vue with the following.

<template>
  <div>
    <h2>Login</h2>
    <p v-if="$route.query.redirect">
      You need to login first.
    </p>
    <form @submit.prevent="login" autocomplete="off">
      <label><input v-model="email" placeholder="email" v-focus></label>
      <label><input v-model="pass" placeholder="password" type="password"></label><br>
      <button type="submit">login</button>
      <p v-if="error" class="error">Bad login information</p>
    </form>
  </div>
</template>

<script>
  import auth from '../auth'
  export default {
    data () {
      return {
        email: '',
        pass: '',
        error: false
      }
    },
    methods: {
      login () {
        auth.login(this.email, this.pass, loggedIn => {
          if (!loggedIn) {
            this.error = true
          } else {
            this.$router.replace(this.$route.query.redirect || '/')
          }
        })
      }
    }
  }
</script>

<style>
  .error {
    color: red;
  }
</style>

To make the v-focus directive on the email field work, add the following to src/main.js (before new Vue({...})).

Vue.directive('focus', {
  // When the bound element is inserted into the DOM...
  inserted: function (el) {
    // Focus the element
    el.focus()
  }
})

Display User Information

Authenticating your users against cloud APIs is cool, but you know what’s even cooler!? Fetching access tokens and showing your users that you know who they are! To do this, you can use the Okta Auth SDK’s OpenID Connect (OIDC) features. Instead of storing the session token in localStorage, you can fetch an access token too. This can be useful when calling APIs that act as resource servers.

You can see how to create a resource server using Spring Boot and Okta in Build a Secure Notes Application with Kotlin, TypeScript, and Okta.

The Okta Auth SDK supports exchanging your session token for an access token. Replace the success callback on authClient.signIn() (in src/auth.js) with the following.

return authClient.signIn({
  username: email,
  password: pass
}).then(response => {
  if (response.status === 'SUCCESS') {
    return authClient.token.getWithoutPrompt({
      clientId: `{clientId}`,
      responseType: ['id_token', 'token'],
      scopes: ['openid', 'email', 'profile'],
      sessionToken: response.sessionToken,
      redirectUri: 'http://localhost:8080'
    }).then(tokens => {
      localStorage.token = tokens[1].accessToken
      localStorage.idToken = tokens[0].idToken
      if (cb) cb(true)
      this.onChange(true)
    })
  }

You’ll need to create an OIDC App in Okta to get a {clientId}. To do this, log in to your Okta Developer account and navigate to Applications > Add Application. Click SPA and click the Next button. Give the app a name you’ll remember, and specify http://localhost:8080 as a Base URI and Login Redirect URI.

OIDC Settings

Click Done and you’ll be shown a screen with this information as well as a Client ID at the bottom. Copy the Client ID into src/auth.js.

You’ll notice the new callback saves the access token as token in localStorage. It also saves the idToken so it can parse the JWT and retrieve the user’s name. You have to add profile to the list of scopes ([‘openid’, ‘email’] is the default) to get the authenticated user’s name.

Once you have the idToken, you can fetch the user’s name from it. Even better, you can use the JWT Inspector Chrome plugin to view JWT values and print them to the JavaScript console.

To see this in action, add the following two methods to src/auth.js to get a person’s name from a decoded JWT. Thanks to Stack Overflow for the decoding JWTs in JavaScript code.

getName () {
  const claims = this.parseJwt(localStorage.idToken)
  console.jwt(localStorage.idToken)
  return claims['name']
 },

parseJwt (token) {
  const base64Url = token.split('.')[1]
  const base64 = base64Url.replace('-', '+').replace('_', '/')
  return JSON.parse(window.atob(base64))
},

WARNING: The code above uses console.jwt(). This only works when you have JWT Inspector installed. Remove or comment this line out when you’re done debugging your code.

Now the ID token will provide you with the user’s name, and you can show it on the dashboard! Modify src/components/Dashboard.vue to have the following code.

<template>
  <div>
    <h2>Dashboard</h2>
    <p>Yay you made it <b>{{name}}</b>!</p>
  </div>
</template>
<script>
  import auth from '../auth'
  export default {
    data () {
      return {
        name: auth.getName()
      }
    }
  }
</script>

If your project is not running, start it with npm start and navigate to http://localhost:8080. Rejoice if it works!

Welcome Okta

Auditing Your Progressive Web App

You can use Lighthouse to audit your application and see if it has all the elements needed for a PWA. To do this, you have to build your project and then run a web server from the dist directory. For example:

npm run build
cd dist
python -m SimpleHTTPServer 8080

If you run Lighthouse on your app at http://localhost:8080, you should see similar results to the screenshot below.

Lighthouse Audits on localhost

Because I like to see what the max possible score is, I deployed this app to Pivotal’s Cloud Foundry. I created a deploy.shscript that replaces the redirectUri (you could also remove it for the same effect) and deploys using cf push.

For this to work, you will have to update Okta to add https://vue-auth-pwa.cfapps.io as a Login Redirect URI (in your OIDC app) and as a Trusted Origin (API > Trusted Origins).

sed -i -e "s|http://localhost:8080|https://vue-auth-pwa.cfapps.io|g" src/auth.js
npm run build
cd dist
touch Staticfile
echo 'pushstate: enabled' > Staticfile
cf push vue-auth-pwa --no-start
cf set-env vue-auth-pwa FORCE_HTTPS true
cf start vue-auth-pwa

Running Lighthouse on https://vue-auth-pwa.cfapps.io yields some pretty good numbers across the board!

Lighthouse Audits on Cloud Foundry

In Closing

I hope you’ve enjoyed this tour of authentication for the incredibly popular Vue.js. Kudos to Evan You for creating it and making it light and fast (perfect for PWAs!).

If you’re intrigued by Vue.js, follow @vuejs and @nuxt_js on Twitter.

You can see the code this lazy developer created for this article on GitHub. You can also check out some other articles I wrote on PWAs.

  • Build Your First Progressive Web Application with Angular and Spring Boot
  • The Ultimate Guide to Progressive Web Applications
Vue.js authentication mobile app

Published at DZone with permission of Matt Raible, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • GitOps: Flux vs Argo CD
  • How To Use Terraform to Provision an AWS EC2 Instance
  • Continuous Development: Building the Thing Right, to Build the Right Thing
  • Spring Cloud: How To Deal With Microservice Configuration (Part 1)

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: