Permissions and Roles using vuejs and laravel

TJ Weems picture TJ Weems · Sep 3, 2018 · Viewed 8.2k times · Source

Here is the current setup I have with CASL. currently it doesnt seem to be reading the rules array I am importing into the ability.js file. I have also imported the ability plugin into the main.js file if you would like to see that as well

here is the ability.js file

import { Ability } from '@casl/ability'

export const ability = new Ability()

export const abilityPlugin = (store) => {
  ability.update(store.state.rules)

  return store.subscribe((mutation) => {
    switch (mutation.type) {
    case 'createSession':
      ability.update(mutation.payload.rules)
      break
    case 'destroySession':
      ability.update([{ actions: 'read', subject: 'all' }])
      break
    }
  })
}

here is the store calling the list of rules

import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
import storage from './utils/storage'
import { abilityPlugin, ability as appAbility } from './utils/ability'

export const ability = appAbility
Vue.use(Vuex)
axios.defaults.baseURL = 'http://todo-laravel.test/api'

export default new Vuex.Store({
  plugins: [
    storage({
      storedKeys: ['rules'],
      destroyOn: ['destroySession']
    }),
    abilityPlugin
  ],
  state: {
    rules: '',
    token: localStorage.getItem('access_token') || null,
  },
  mutations: {
    createSession(state, role) {
      state.rules = role[0]
    },
    destroySession(state) {
      state.rules = ''
    },
    retrieveToken(state, token) {
      state.token = token
    },
    destroyToken(state) {
      state.token = null
    },
  },
  actions: {
    destroyToken(context) {
      axios.defaults.headers.common['Authorization'] = 'Bearer ' + context.state.token

      if (context.getters.loggedIn) {
        return new Promise((resolve, reject) => {
          axios.post('/logout')
          .then(response => {
            localStorage.removeItem('access_token')
            context.commit('destroyToken')
            context.commit('destroySession')
            resolve(response)
          })
          .catch(error => {
            localStorage.removeItem('access_token')
            context.commit('destroyToken')
            reject(error)
          })
        })
      }
    },
    retrieveToken({ commit, dispatch }, credentials) {

      return new Promise((resolve, reject) => {
          axios.post('/login', {
              username: credentials.username,
              password: credentials.password,
          })
          .then(response => {
              const token = response.data.access_token

              localStorage.setItem('access_token', token)
              commit('retrieveToken', token)
              dispatch('retrieveRules')
              resolve(response)
          })
          .catch(error => {
              console.log(error)
              reject(error)
          })
      })
    },
    retrieveRules(context) {
      axios.defaults.headers.common['Authorization'] = 'Bearer ' + context.state.token

      return new Promise((resolve, reject) => {
          axios.get('/rules')
          .then(response => {
              console.log(response.data)
              context.commit('createSession', response.data)
              resolve(response)
          })
          .catch(error => {
              console.log(error.response.data)
              reject(error)
          })
      })
    },

here is the array of rules being saved in storage

[{id: 1, role_id: 3, action: "Manage", subject: "All"}]

Answer

Sergii Stotskyi picture Sergii Stotskyi · Sep 11, 2018

You need to define subjets and actions which can be done on that subjects (e.g., user can read post translates to -> subject: "post", action: "read"). When you do it, you can create a route (or return it as part of authentication response) for authenticated user on API side which returns user specific actions. For example:

if user is an admin, he can do everything, so the respons should be:

{
  "rules": [
    { "action": "manage", "subject": "all" } 
  ]
}

if user is a team member then he has some restricted rights (lets assume he can only read settings):

{
  "rules": [
    { "action": "read", "subject": "Settings" } 
  ]
}

On UI side:

if you don't use Vuex:

// main.js
import { abilitiesPlugin } from '@casl/vue'
import Vue from 'vue'

Vue.use(abilitiesPlugin);

// inside LoginComponent
{
  methods: {
    login() {
      return this.http.post(...)
        .then(respose => this.$ability.update(response.data.rules)
    }
  }
}

if you use Vuex:

// main.js
import { abilitiesPlugin } from '@casl/vue'
import Vue from 'vue'
import ability from './services/ability';

Vue.use(abilitiesPlugin, ability);


// services/ability.js
import { Ability } from '@casl/ability';

export default new Ability()


// store.js
import ....
import ability from './services/ability'

const updateAbilities = (store) => {
  ability.update(store.state.rules) // take rules from your state structure

  return store.subscribe((mutation) => {
    switch (mutation.type) {
    case 'login':
      ability.update(mutation.payload.rules)
      break
    case 'logout':
      ability.update([{ actions: 'read', subject: 'all' }]) // read only mode
      // or `ability.update([])` to remove all permissions
      break
    }
  })
}

export default new Vuex.Store({
  plugins: [
    updateAbilities
  ],
  // ... your store declaration
})

You can take a look at https://github.com/stalniy/casl-vue-api-example which is an example of Vue, CASL and Rails integration to see some details

Update:

Later in all components just use $can function or <can> component to check permissions. Check CASL Vue documentation for details