The original published on http://blog.ahui.me/posts/2018-03-26/permission-control-of-vuejs/

In many B-end applications, such as the management background of small enterprises, or large CMS and CRM systems, permission management is a top priority. Most Web applications in the past adopt the mode of server template + server route, and permission management is naturally controlled and filtered by the server. However, in the trend of the separation of the front and back ends, if the single-page application development mode is adopted, the front-end will inevitably cooperate with the server side to carry out permissions management. Next, we will take vuejS single-page application development as an example, and give some try schemes, hoping to provide some ideas for everyone. Note that the use of NodeJS as the front and back end separation of the middle tier is beyond the scope of this article.

The target

As for permission management, I can only summarize from my previous project experience, which is not necessarily very accurate, because I am not very familiar with the server.

General rights management is divided into the following parts.

  • Application right
  • Page level permissions
  • Module level permissions
  • Interface level Permission

We’ll go through each of these sections one by one. The complete instance code is hosted on github-funkyLover/ vue-Permission-Control-demo.

Application usage rights – Login status management and storage

First of all, using the right to use is a simple matter of judging login status. In many C-side applications, the ability to use more functions after logging in can be part of permission management to some extent. However, in the B-end application, it can not be used without logging in (of course, it can also use functions like retrieving passwords).

In the past, the login status was managed by session+cookie/token. When a user opened a web page, the user would bring the cookie/token, which was determined by the back-end logic and redirected. In SPA mode, the page jump is controlled by the front end route, and the front end needs to actively send an automatic login request to judge the user status and jump according to the returned result.

The logic of automatic login can be implemented in depth. For example, after a successful login, the user information is encrypted and shared between multiple tabs through localstorage, so that when a new TAB is opened, there is no need to automatically log in again. Here is the simplest implementation to explain, the basic process is as follows:

  1. The user requests a page resource
  2. Check whether the local cookie/localstorage has a token
  3. Without a token, the login route is always redirected to regardless of which route the user requests to open
  4. If a token is detected, request it firstAutomatic loginAccording to the returned result, determine whether to enter the route requested by the user or jump to the login route

Generally, the user status should be judged based on the login route (including the route that forgets the password) and other routes. On the premise of [email protected], the user status can be judged on the beforeEach hook of the router and the route can be switched. Here is part of the code:

const routes = [
  {
    path: '/'.component: Layout,
    children: [{path: ' '.name: 'Dashboard'.component: Dashboard
      }, {
        path: 'page1'.name: 'Page1'.component: Page1
      }, {
        path: 'page2'.name: 'Page2'.component: Page2
      }
    ]
  }, {
    path: '/login'.name: 'Login'.component: Login
  }
]

const router = new Router({
  routes,
  mode: 'history'
  // Other configurations
})

router.beforeEach((to, from, next) = > {
  if (to.name === 'Login') {
    // If the entry route is login, check whether you have logged in
    if (store.getters.user.isLogin) {
      // If you have logged in, go to the function page
      return next('/')}else {
      return next()
    }
  } else {
    if (store.getters.user.isLogin) {
      return next()
    } else {
      // If there is no login, enter the login route
      return next('/login')}}})Copy the code

After the jump logic is set up, we need to check whether there is a token in the login route and perform automatic login

// Login.vue
async mounted () {
  var token = Cookie.get('vue-login-token')
  if (token) {
    var { data } = await axios.post('/api/loginByToken', {
      token: token
    })
    if (data.ok) {
      this[LOGIN]()
      Cookie.set('vue-login-token', data.token)
      this.$router.push('/')}else {
      Logon failure logic}}},methods: {
  ...mapMutations([
    LOGIN
  ]),
  async login () {
    var { data } = await axios.post('/api/login', {
      username: this.username,
      password: this.password
    })
    if (data.ok) {
      this[LOGIN]()
      Cookie.set('vue-login-token', data.token)
      this.$router.push('/')}else {
      // Logon error logic}}}Copy the code

Similarly, empty the token when you log out. Note that the implementation of logic presented here is relatively rough, and actual changes should be made according to requirements, such as giving users proper prompts for automatic login, putting the logic of reading/storing tokens into the store for unified management, and dealing with the outdated logic of tokens.

Page level permissions – Generate router objects based on permissions

This can be handled with vue-router/ router exclusive guards. The basic idea is to set up the beforeEnter hook function in each route that needs to check permissions and determine the user’s permissions in it.


const routes = [
  {
    path: '/'.component: Layout,
    children: [{path: ' '.name: 'Dashboard'.component: Dashboard
      }, {
        path: 'page1'.name: 'Page1'.component: Page1,
        beforeEnter: (to, from, next) = > {
          // Check permissions and jump
          next()
        }
      }, {
        path: 'page2'.name: 'Page2'.component: Page2,
        beforeEnter: (to, from, next) = > {
          // Check permissions and jump
          next()
        }
      }
    ]
  }, {
    path: '/login'.name: 'Login'.component: Login
  }
]
Copy the code

The above code is sufficient to complete the requirements, and combined with vue-Router/route lazy loading can also realize that the corresponding page component resources will not be loaded for the route without permission. However, there are some problems with this implementation.

  1. When page permissions are detailed enough, the router configuration becomes larger and harder to maintain
  2. Whenever the background updates the page permission rules, the front-end judgment logic also changes, which is equivalent to the front and back end need to jointly maintain a set of page level permissions.

The first problem can be mitigated by coding, such as putting logic in beforeEach hooks, or abstracting permission checking logic with higher-order functions. However, the second problem is unavoidable. If we only configure the route on the back-end, and the front-end extends the router according to the configuration returned by the back-end, then we can avoid the common maintenance of a set of logic on the front and back ends. According to this idea, we rewrite the previous logic.

// Login.vue
async mounted () {
  var token = Cookie.get('vue-login-token')
  if (token) {
    var { data } = await axios.post('/api/loginByToken', {
      token: token
    })
    if (data.ok) {
      this[LOGIN]()
      Cookie.set('vue-login-token', data.token)
      // Call the update router method here
      this.updateRouter(data.routes)
    }
  }
},
// ...
methods: {
  async updateRouter (routes) {
    // routes Is the route information returned from the background
    const routers = [
      {
        path: '/'.component: Layout,
        children: [{path: ' '.name: 'Dashboard'.component: Dashboard
          }
        ]
      }
    ]
    routes.forEach(r= > {
      routers[0].children.push({
        name: r.name,
        path: r.path,
        component: (a)= > routesMap[r.component]
      })
    })
    this.$router.addRoutes(routers)
    this.$router.push('/')}}Copy the code

This allows you to dynamically extend routing based on the return of the back end, and of course generate sidebar or top bar navigation menus based on the return of the back end, eliminating the need to deal with page permissions in the front end. Again, it is important to note that the examples in this article only implement the most basic features and omit much of the logic that can be optimized

  1. This is repeated every time a new TAB (non-login route) is openedAutomatic loginAnd re-extend the router
  2. Every time you open a new TAB, the automatic login will still jump to/Route, even if the newly opened URL is/page1

The solution is to store the user login information and route information in localstorage. When a new TAB is opened, a Router object is generated based on the information stored in localstorage. Plug-ins such as Store. js and vuex-shared-mutations can simplify this logic to a certain extent, which will not be discussed here.

Module level permissions – Component permissions

Module-level permissions are well understood as components with permission judgments. Using high-order components in React to define components that require filtering permissions is simple and easy to understand. Take a look at the following example

const withAuth = (Comp, auth) = > {
  return class AuthComponent extends Component {
    constructor(props) {
      super(props);
      this.checkAuth = this.checkAuth.bind(this)
    }

    checkAuth () {
      const auths = this.props;
      returnauths.indexOf(auth) ! = =- 1;
    }

    render () {
      if (this.checkAuth()) { <Comp { ... this.props }/> }else {
        return null}}}}Copy the code

The above example shows that the component can be displayed with limited permissions, and hidden components can be defined to handle higher-order components according to different permission filtering requirements.

In Vuejs you can do this using the render function

// Auth.vue import { mapGetters } from 'vuex' export default { name: 'Auth-Comp', render (h) { if (this.auths.indexOf(this.auth) ! == -1) { return this.$slots.default } else { return null } }, props: { auth: String }, computed: { ... MapGetters (['auths'])}} // Use <Auth Auth ="canShowHello"> <Hello></Hello> </Auth>Copy the code

The Render function in Vuejs provides full programming capability and even JSX syntax in the render function for a React development experience. See the vuejs documentation/render function & JSX for details.

Interface level Permission

Interface-level permissions are generally not associated with the UI library, so here’s how to handle them.

  1. The first step is to get permission from the back end for the Api interface that the current user is allowed to access
  2. Configure interceptors for the front-end Ajax request library (such as AXIos) based on the returned results
  3. Determine permissions in the interceptor and prompt the user as required
axios.interceptors.request.use((config) = > {
  // Check the permissions
  if (/* No permission */) {
    return Promise.reject('no auth')}else {
    return config
  }
}, err => {
  return Promise.reject(err)
})
Copy the code

In fact, I think it is not necessary for the front end to judge the permission of the REQUESTED API. After all, the interface is not like routing, routing has been managed by the front end, but the interface ultimately needs to pass the server’s verification. You can add as needed.

Afterword.

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *