Quản lý user permission trong VueJS
Trong việc authen trên frontend, chúng ta thường xuyên muốn thay đổi những cái mà được hiển thị cho người dùng tùy theo role. VD như chúng ta chỉ cho phép người dùng thấy bài đăng, còn tác giả hoặc quản trị thì được thấy thêm nút chỉnh sửa chẳng hạn.
Quản lý permission trong front end là một việc cực kỳ phức tạp. Trước đây bạn chắc đã từng viết:
if (user.type === ADMIN || user.auth && post.owner === user.id ) { ... }
Thay vào đó bạn có thể sử dụng 1 thư viện nhỏ gọn tên là CASL giúp quản lý quyền người dùng rất đơn giản. Một khi bạn đã define quyền của mình với CASL, và set nó cho người dùng đang hoạt động, bạn có thể thực hiện bất kỳ hành động nào cũng được:
if (abilities.can('update', 'Post')) { ... }
Trong bài viết này, mình sẽ hướng dẫn các bạn quản lý quyền người dùng với Vue.js và CASL.
CASL
CASL cho phép bạn define 1 set các rule mà nó có thể quy định những resource nào mà user được phép access.
Ví dụ, các rule của CASL có thể cho phép các hành động CRUD (Create, Read, Update và Delete) mà 1 user có thể thực hiện trên 1 resource hoặc data nào đó (post, comment…).
Giả sử chúng ta có 1 trang tin tức với 1 bộ các quy tắc sau:
- Khách có thể xem bất kỳ post nào
- Admin có thể xem tất cả các post, có thể update hoặc xóa post.
Trong CASL, chúng ta sử dụng AbilityBuilder
để define các rule.
const { AbilityBuilder } = require('casl'); export function(type) { AbilityBuilder.define(can => { switch(type) { case 'guest': can('read', 'Post'); break; case 'admin': can('read', 'Post'); can(['update', 'delete'], 'Post'); break; // Add more roles here } } };
Bây giờ bạn có thể control dựa trên các rule mà mình đã xác định:
import defineAbilitiesFor from './abilities'; let currentUser = { id: 999, name: "Hai" type: "registered", }; let abilities = defineAbilitiesFor(currentUser.type); Vue.component({ template: `<div v-if="showPost"><div> <div v-else>Please log in</div> `, props: [ 'post' ], computed: { showPost() { return abilities.can('read', 'Post'); } } });
Xem thêm tại đây nhé. CASL
Demo
Bây giờ mình sẽ làm 1 demo đơn giản nhé. Rule của ứng dụng là user có thể đọc tất cả các post hoặc tạo post mới, nhưng chỉ được update và xóa post do chính mình tạo ra. Mình sẽ dùng Vue.js với CASL để dễ dàng tạo rule và dễ dàng thêm action trong tương lai khi cần.
Xác định những quyền của người dùng
Chúng ta xác định quyền của user trong file resources/ability.js
. Chúng ta sẽ định nghĩa các rule là một module CommonJS để đảm bảo tương thích với Node (Webpack có thể chuyển đổi thành module sử dụng trên client).
const casl = require('casl'); module.exports = function defineAbilitiesFor(user) { return casl.AbilityBuilder.define( { subjectName: item => item.type }, can => { can(['read', 'create'], 'Post'); can(['update', 'delete'], 'Post', { user: user }); } ); };
Truy cập các quyền trong Vue
Thực hiện các bước sau:
- Import Vue và abilities plugin. Plugin này sẽ thêm CASL với Vue prototype, cho phép chúng ta gọi nó bên trong các component.
- Import tập rules vào Vue app (
resources/abilities.js
). - Gán cho người dùng hiện tại, trên thực tế thì chúng ta sẽ lấy data user từ server, trong ví dụ này mình mặc định data trong code luôn.
- Hãy nhớ rằng, module
abilities
xuất ra 1 hàm mà chúng ta sẽ gọi đến làdefineAbilitiesFor
. Chúng ta sẽ truyền object user đến hàm này. Bất cứ khi nào chúng ta kiểm tra một đối tượng, chúng ta có thể thấy những permission nào được phép cho user. - Thêm abilities plugin, cho phép chúng ta kiểm tra bên trong component giống như thế này
this.$can(...)
Bây giờ thêm đoạn code bên dưới trong src/main.js
import Vue from 'vue'; import abilitiesPlugin from './ability-plugin'; const defineAbilitiesFor = require('../resources/ability'); let user = { id: 1, name: 'Hai' }; let ability = defineAbilitiesFor(user.id); Vue.use(abilitiesPlugin, ability);
Quy định vài thứ cho Post
Cần có 2 thuộc tính mà 1 post cần có:
- Thuộc tính type. CASL sẽ sử dụng 1
subjectName
call back đã define trongabilities.js
để check thể loại post đang được kiểm tra. - Thuộc tính của người dùng. Nếu là author thì có quyền update hoặc xóa. Trong main.js chúng ta đã cho CASL biết người dùng hiện tại là ai với
defineAbilitiesFor(user.id)
. Những việc CASL cần làm là check id của user có phù hợp hay không rồi gán quyền cho họ.
let posts = [ { type: 'Post', user: 1, content: 'User Hai là ID = 1, chỉ có thể có quyền update hoặc xóa post này' }, { type: 'Post', user: 2, content: 'User Hai chỉ đọc được và không có quyền gì trong post này' } ];
Kiểm tra quyền user trên một object
Chúng ta thêm đoạn code bên dưới vào file src/components/Post.vue
:
<template> <div class="post"> <div class="content"> <br/><small>posted by </small> </div> <button @click="del">Delete</button> </div> </template> <script> import axios from 'axios'; export default { props: ['post', 'username'], methods: { del() { if (this.$can('delete', this.post)) { ... } else { this.$emit('err', 'Only the owner of a post can delete it!'); } } } } </script> <style lang="scss">...</style>
Khi user click nút xóa, hành độc click sẽ dc method del
xử lý, sau đó chúng ta sử dụng CASL để check nếu người dùng hiện tại có quyền xóa hay không thông qua this.$can('delete', post)
. Nếu họ có quyền thì xử lý vài action. Nếu không có thì show thông báo lỗi “Only the owner of a post can delete it!”.
Thử nghiệm trên Server-side
Những bướv bên trên thì hoàn toàn làm ở client side để cho tiện và nhanh, bây giờ chúng ta thử trên server xem sao. Nếu user xóa 1 post ở front end, chúng ta sẽ sử dụng AJAX để gửi request lên server thông qua API. Ta thêm 1 ít code trong file src/components/Post.vue
if (this.$can('delete', post)) { axios.get(`/delete/${post.id}`, ).then(res => { ... }); }
Sau đó chúng ta để CASL test logic trên server, ta thêm đoạn code sau vào file server.js
app.get("/delete/:id", (req, res) => { let postId = parseInt(req.params.id); let post = posts.find(post => post.id === postId); if (ability.can('delete', post)) { posts = posts.filter(cur => cur !== post); res.json({ success: true }); } else { res.json({ success: false }); } });
Vì CASL là isomorphic (chạy được cả server và phía browser chung một mã nguồn hay còn gọi là universal), đối tượng ability
trên server có thể được import từ abilities.js
, đỡ bị đúp code.
Tổng kết
Nói chung là dùng thư nhỏ nhẹ như CASL sẽ giúp ta tiết kiệm nhiều thời gian để làm sản phẩm, code dễ đọc hơn. Cám ơn bạn đã đọc!
TopDev lược dịch từ vuejsdevelopers.com
- G Giải Quyết Bài Toán Kinh Doanh Bằng Big Data và AI
- B BenQ RD Series – Dòng Màn Hình Lập Trình 4k+ Đầu Tiên Trên Thế Giới
- F Framework nào tốt nhất cho dự án của bạn? – Checklist chi tiết
- K Kinh nghiệm xử lý responsive table hiệu quả
- S Stackoverflow là gì? Bí kíp tận dụng Stack Overflow hiệu quả
- 7 7 kinh nghiệm hữu ích khi làm việc với GIT trong dự án
- B Bài tập Python từ cơ bản đến nâng cao (có lời giải)
- B Bảo mật API là gì? Một số nguyên tắc và kỹ thuật cần biết
- H Hướng dẫn cài đặt và tự học lập trình Python cơ bản từ A-Z
- C Chinh Phục Phân Tích Dữ Liệu Với Pandas Trong Python: Hướng Dẫn Từng Bước