Authentication với Vue và Vuex

Chúng ta có những câu hỏi sau

  • Lưu lại token như thế nào?
  • Làm cách nào để redirect user sau khi authen
  • Làm sao để chặn user truy cập một số route nếu chưa authen

Chúng ta sẽ cùng xem xét các vấn đề trên, áp dụng trong dự án sử dụng Vue, Vuex

Tuy nhiên cũng nên nhớ rằng mỗi project sẽ sử dụng luồng authen khác nhau. Nên không nên áp dụng cứng nhắc các cách được đề xuất trong bài viết này.

Trước khi đọc bài này mình khuyến cáo bạn nắm rõ kiến thức căn bản của Vuex trong bài viết này

Khai báo Vuex Auth Module

Một số giá trị chúng ta cần đưa vào store:

  • token: lúc khởi tạo nếu có trong localStorage thì lấy giá trị này
  • status: trạng thái của network request (loading, success, error)
const state = {
  token: localStorage.getIte('user-token') || '',
  status: ''
}

Getters để lấy giá trị state này

const getters = {
  isAuthenticated: state => !!state.token,
  authStatus: state => state.status
}

Actions xử lý login, logout

const actions = {
  [AUTH_REQUEST]: ({commit, dispatch}, user) => {
    // Dùng promise để sau khi login thành công
    // chúng ta có thể redirect user đi đến trang khác
    return new Promise((resolve, reject) => { 
      commit(AUTH_REQUEST)
      axios({url: 'auth', data: user, method: 'POST' })
        .then(resp => {
          const token = resp.data.token
                localStorage.setItem('user-token', token)
                // thêm header authorization:
                axios.defaults.headers.common['Authorization'] = token
                commit(AUTH_SUCCESS, resp)
                dispatch(USER_REQUEST)
                resolve(resp)
        })
      .catch(err => {
        commit(AUTH_ERROR, err);
        // nếu có lỗi, xóa hết token đang có trên trình duyệt
        localStorage.removeItem('user-token');
        reject(err);
      })
    })
  },
  [AUTH_LOGOUT]: ({commit, dispatch}) => {
    return new Promise((resolve, reject) => {
      commit(AUTH_LOGOUT);
      localStorage.removeItem('user-token');
      delete axios.defaults.headers.common['Authorization'];
      resolve();
    })
  }
}

Mutations thực hiện update lại store

const mutations = {
  [AUTH_REQUEST]: (state) => {
    state.status = 'loading'
  },
  [AUTH_SUCCESS]: (state, token) => {
    state.status = 'success'
    state.token = token
  },
  [AUTH_ERROR]: (state) => {
    state.status = 'error'
  },
}

Tự động authen

Hiện tại nếu chúng ta f5 lại, chúng ta vẫn có token bên trong store (do lấy từ localStorage). Tuy nhiên authorization header trong Axios chưa được set

const token = localStorage.getItem('user-token')
if (token) {
  axios.defaults.headers.common['Authorization'] = token
}

Authen Route

Chúng ta chắc chắn sẽ muốn cấp quyền vào một số route chỉ khi có token

Ví dụ đã đăng nhập sẽ được vào /account, chưa đăng nhập có thể vào /login/

import store from '../store' // your vuex store 

const ifNotAuthenticated = (to, from, next) => {
  if (!store.getters.isAuthenticated) {
    next()
    return
  }
  next('/')
}

const ifAuthenticated = (to, from, next) => {
  if (store.getters.isAuthenticated) {
    next()
    return
  }
  next('/login')
}

export default new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home,
    },
    {
      path: '/account',
      name: 'Account',
      component: Account,
      beforeEnter: ifAuthenticated,
    },
    {
      path: '/login',
      name: 'Login',
      component: Login,
      beforeEnter: ifNotAuthenticated,
    },
  ],
})

Xử lý các tình huống khác

Các tình huống khác có thể xảy ra như token hết hạn, user chưa được quyền gọi API

Sử dụng Axios, chúng ta có option là interceptors, nó như lính gác cổng, tất cả những response trả về từ network request sẽ đi qua đây. Chúng ta kiểm tra tất cả request nào trả về lỗi HTTP 401, dispatch ra logout action

axios.interceptors.response.use(undefined, function (err) {
    return new Promise(function (resolve, reject) {
      if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
      // nếu là lỗi chưa đăng nhập unauthorized, gọi lên logout
        this.$store.dispatch(AUTH_LOGOUT)
      // có thể đưa user về trang đăng nhập ở đây!
      }
      throw err;
    });
  });

Toàn bộ source code

Tham khảo

Authentication Best Practices for Vue

Structuring a Vue project — Authentication