/appid-vue-client

A VueJS client sample for IBM App ID

Primary LanguageVue

vue-client

This is a sample web application using VueJS to access IBM App ID. It demonstrates how to access a secured resource from an API server as well as how to secure VueJS routes.

This work is based upon the methods used for an Angular sample in the Securing Angular+Node.js Applications using App ID blog post.

Project setup

This project requires the sample api-server in order to demonstrate access to a secured API resource.

npm install

Compiles and hot-reloads for development

npm run serve

Changes

This section documents what you need to do as a front end developer to use the code from this sample in your own applications.

The code in this repository was generated by the vue cli (version 3.0.5) by running:

vue create vue-client

To make the code sample as simple as possible all of the default options (babel and linting) were disabled and the minimal options required for this sample, Router and Vuex, were selected.

store.js

We use the store from VueX to deliver the equivalent of a service in Angular i.e. it creates stateful access to some variables. In this case we add something to remember whether the user has logged in already.

This simply provides a convenience mechanism and speed-up to the VueJS application. The api server will record which users(s) are logged in and store this in a session store. Hence, storing this state in VueJS simply means we can create the option for one fewer calls to the api server or essentially stops us having to call the api server on every route change in the VueJS application. Hence, if this VueJS state disappears at any time or if the user hops over to another instance of the front end in the cloud then they will see no difference to their experience as the server will be contacted for its version of the session state instead.

Add a new state:

user: {
  logged: false,
  loggedInAs: {
    name: "",
    email: ""
  }
}

State in VueJS is updated via mutations. So we add a function to update the user state created above:

setUser(state, u) {
  state.user = u;
}

components/protected.vue

Create a new component. In the sample we call this protected which has the double purpose of demonstrating:

  1. how to secure a route in VueJS
  2. how to access a secured API on a server

This component will be protected via a VueJS requireAuth call specified to beforeEnter when routing to the component (see below). This demonstrates how to secure a VueJS route.

This component makes a call to a secured API end point and displays the result in the browser window. In the sample the end point /protected/get-some-info is accessed which (if successfully authenticated) will return a JSON payload of {"hello":"world"}. The important thing to note when accessing secured resources is that you pass through the credentials to the api server as part of your GET request. In the code sample we use the fetch browser API which has credentials: 'include' specified.

<template>
  <div class="about">
    <h1>This is a protected page</h1>
    <h2>hello: {{ hello }}</h2>
  </div>
</template>

<script>
export default {
  data: function () {
    return {
      hello: undefined
    }
  },
  computed: {
    user () {
      return this.$store.state.user
    }
  },
  methods: {
    getProtectedAPI () {
      fetch('http://localhost:3000/protected/get-some-info',{
            credentials: 'include',
          }).then(res => res.text())
          .then(body => {
            console.dir(body)
            this.hello = JSON.parse(body).hello
          })
    },
  },
  created() {
    this.getProtectedAPI()
  }
}
</script>

router.js

Add 2 new imports:

import Protected from '@/components/Protected'
import store from './store'

Add a new function requiredAuth as follows:

// https://router.vuejs.org/guide/advanced/navigation-guards.html
function requireAuth(to, from, next) {
  // Testing authentication state of the user
  if (!store.state.user.logged) {
    // Not sure if user is logged in yet, testing their login
    const isLoggedUrl = "http://localhost:3000/auth/logged"
    fetch(isLoggedUrl, {credentials: 'include'}).then(res => res.json()).then(isLogged => {
      if (isLogged.logged) {
        // User is already logged in, storing
        store.commit("setUser", isLogged)
        next()
      } else {
        // User is not logged in, redirecting to App ID
        window.location.href=`http://localhost:3000/auth/login?redirect=${to.fullPath}`
      }
    }).catch(e => {
      // TODO: do something sensible here so the user sees their login has failed
      console.log("Testing user login failed - D'oh!")
    })
  } else {
    // User already logged in
    next()
  }
}

Any time you want to secure a route the requireAuth function must be called first. This is done in the code sample by demonstrating a new route called protected with the code:

{
  path: '/protected',
  name: 'protected',
  beforeEnter: requireAuth,
  component: Protected
}