Okta Auth JS and Vue

On This Page

This guide will walk you through integrating authentication into a Vue app with Okta by performing these steps:

This guide is for @okta/okta-auth-js >= v4.1.0 and < 5.0.0.

Prerequisites

If you do not already have a Developer Edition Account, you can create one at https://developer.okta.com/signup/ (opens new window).

Add an OpenID Connect Client in Okta

  • Sign in to the Okta Developer Dashboard, and select Create New App
  • Choose Single Page App (SPA) as the platform, then populate your new OpenID Connect application with appropriate values for your app. For example:
Setting Value
App Name OpenID Connect App (must be unique)
Login redirect URIs http://localhost:8080/login/callback
Logout redirect URIs http://localhost:8080/
Allowed grant types Authorization Code

Note: It is important to choose the appropriate application type for apps which are public clients. Failing to do so may result in Okta API endpoints attempting to verify an app's client secret, which public clients are not designed to have, hence breaking the sign-in or sign-out flow.

Note: CORS is automatically enabled for the granted login redirect URIs.

Create a Vue App

To quickly create a Vue app, we recommend the Vue CLI. Follow their guide (opens new window) or use the steps below.

npm install -g @vue/cli
vue create okta-vue-auth-example
# Manually select features: choose defaults + Router
# Choose history mode for router
cd okta-vue-auth-example

Install Dependencies

A simple way to add authentication to a Vue app is using the Okta Auth JS library. You can install it via npm:

npm install @okta/okta-auth-js

Create a Custom Sign-In Form

If the Okta Sign-In Widget does not fit your needs, AuthJS provides lower-level access to User Lifecycle operations, MFA, and more. For this example, you'll create a simple username and password form without MFA.

Create src/components/About.vue with 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>

Create a src/auth.js file:

const OktaAuth = require('@okta/okta-auth-js').OktaAuth
const authClient = new OktaAuth({issuer: 'https://{yourOktaDomain}'})

export default {
  login (email, pass, cb) {
    cb = arguments[arguments.length - 1]
    if (localStorage.token) {
      if (cb) cb(true)
      this.onChange(true)
      return
    }
    return authClient.signInWithCredentials({
      username: email,
      password: pass
    }).then(transaction => {
      if (transaction.status === 'SUCCESS') {
        return authClient.token.getWithoutPrompt({
          clientId: '{clientId}',
          responseType: ['id_token', 'token'],
          scopes: ['openid', 'email', 'profile'],
          sessionToken: transaction.sessionToken,
          redirectUri: window.location.origin + '/login/callback'
        }).then(response => {
          localStorage.token = response.tokens.accessToken
          localStorage.idToken = response.tokens.idToken
          if (cb) cb(true)
          this.onChange(true)
        })
      }
    }).catch(err => {
      console.error(err.message)
      if (cb) cb(false)
      this.onChange(false)
    })
  },

  getToken () {
    return localStorage.token
  },

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

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

  onChange () {
  }
}

You’ll need to replace {yourOktaDomain} with your Okta domain in the code above. Replace {clientId} with the client ID from the app you created in the beginning.

Change src/App.vue to have the following code:

<template>
  <div id="app">
    <h1>Okta Auth JS Example</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>

<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>

Add a src/components/Login.vue to render your sign-in form:

<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()
  }
})

Create Routes

Some routes require authentication in order to render. Let's take a look at what routes are needed for this example:

  • /: The default landing page.
  • /about: A simple about page.
  • /dashboard: A route that's protected.
  • /login: The sign-in form.
  • /logout: A route to logout and redirect back to the default page.

Create src/router/index.js with the following code.

import Vue from 'vue'
import VueRouter 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(VueRouter)

export default new VueRouter({
  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()
  }
}

Start your app

Finally, start your app:

npm run serve

Conclusion

You have now successfully authenticated with Okta! Now what? With a user's id_token, you have basic claims for the user's identity. You can extend the set of claims by modifying the scopes to retrieve custom information about the user. This includes locale, address, groups, and more.

Want to learn how to use the user's access_token? Check out our Vue How To Guide to learn about protecting routes on your server, validating the access_token, and more!

Support

Have a question or see a bug? Post your question on the Okta Developer Forum (opens new window).