All Articles

Vue Authentication with FeathersJS

padlocks

FeathersJS Authentication

Authenticating against a FeathersJS server is pretty simple, I am going to use the local strategy which is by default email/password based as my examples. In order to authenticate against a typical setup you would via rest post to /authenticate which will give you an accessToken as a response which you can add to your http Authorization header if you are doing a standard REST request.

{  
  "strategy": "local",  
  "email": "user@local.dev",  
  "password": "1234"  
}

There is a however a client library for FeathersJS which will help with connecting to a FeathersJS API and you can use it like:

import feathers from '@feathersjs/feathers'
import socketio from '@feathersjs/socketio-client'
import auth from '@feathersjs/authentication-client'
import io from 'socket.io-client'
export const host = process.env.API_HOST || 'http://localhost:3030'
const socket = io(host, { transports: ['websocket'] })
const feathersClient = feathers()
.configure(socketio(socket))
.configure(auth({ storage: window.localStorage }))
export default feathersClient
view raw feathers-client.js hosted with ❤ by GitHub

Vue & Vuex

Right now lets talk about using FeathersJS services within Vuex I will be using feathers-vuex to match up Vuex and FeathersJS. Similar setup to my previous article except we will need to authenticate on the client. Let me show you first how to add the FeathersJS services to Vuex

import Vue from 'vue'
import Vuex from 'vuex'
import feathersVuex from 'feathers-vuex'
import feathersClient from './feathers-client'
const { service, auth, FeathersVuex } = feathersVuex(feathersClient, { idField: '_id', enableEvents: true })
Vue.use(Vuex)
Vue.use(FeathersVuex)
Vue.config.devtools = true
export default new Vuex.Store({
plugins: [
service('users', { paginate: true }),
service('movies', { paginate: true }),
auth({ userService: 'users' })
]
})
view raw store.js hosted with ❤ by GitHub

We pull in the feathers-client which defines the setup to the FeathersJS API, and we can then add each service which will add a namespaced module to Vuex. A very simple look at a basic component which will fetch data from the API and will read from the store using a reactive getter:

<template>
<div class="movies">
<ul>
<li v-for="movie in movies" :key="movie._id">
{{ movie.title }}
</li>
</ul>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
export default {
name: 'movies',
computed: {
...mapGetters('movies', {
findMoviesInStore: 'find'
}),
movies () {
return this.findMoviesInStore({ query: {} }).data
}
},
methods: {
...mapActions('movies', {
findMovies: 'find'
})
},
created () {
this.findMovies({ query: {} })
}
}
</script>
view raw Movie.vue hosted with ❤ by GitHub

Use a mapActions namespace, and then pull in the action I wish to use, so here I am using the find action so I can lookup data from the API. I call this within the created lifecycle hook so the store is populated with data from that query. The mapGetters then pulls in data using the same query used to get the data from the API and if you have setup events/channels within your FeathersJS API will get updated when events are fired.

Authentication

I realise I have gone off topic a little, so let’s get back on track with Authentication. When calling using the FeathersJS client libraries, if setup with LocalStorage it will automatically store the token from the authenticate request, this means that upon a browser refresh if you call authenticate without any params it will use the token from the LocalStorage and if valid you will be authenticated.

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store/index'
Vue.config.productionTip = false
router.beforeEach((to, from, next) => {
const currentUser = store.state.auth.user
const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
if (requiresAuth && !currentUser) {
next('/login')
} else {
next()
}
})
// Auth first before loading the app
store.dispatch('auth/authenticate')
.catch(() => {})
// Render the app
.then(() => {
// eslint-disable-next-line no-new
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
})
view raw main.js hosted with ❤ by GitHub

Here I try to authenticate using the LocalStorage token, and ignore if it fails, this will populate the payload within the auth module of the store. I run the authenticate before I initiate the Vue app so that any subsequent requests within components are authenticated (should the initial authentication request return successful).

I also setup a beforeEach on the router to check whether we have a valid user in the store and if the route requires authentication. Here is how I would setup some basic navigation to show links to logged in users and non-logged in users

<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link>
<router-link v-if="!user" to="/login">Login</router-link>
<router-link v-if="user" to="/account">Account</router-link>
<a v-if="user" @click.prevent="handleLogout" href="#">Logout</a>
</div>
<router-view/>
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default {
name: 'app',
computed: {
user () {
return this.$store.state.auth.user
}
},
methods: {
...mapActions('auth', ['logout']),
handleLogout () {
this.logout()
.then(_ => {
this.$router.push('/')
})
}
}
}
</script>
view raw App.vue hosted with ❤ by GitHub

I use a computed property to map to the user within the auth module of Vuex so that if a user is logged in or logs out this will update the navigation without having to handle watching any changes.

Mattchewone/feathersjs-vuex-demo-ui

Mattchewone/feathersjs-vuex-demo-api

This is a work in progress, so if you notice anything wrong or even better any improvements then please let me know! :)

Enjoy!