Chương 37. Authorization
Chương trước nói về authentication:
Bạn là ai?
Chương này nói về authorization:
Bạn được làm gì?
Hai chuyện này rất dễ bị trộn vào nhau.
Ví dụ:
User đã đăng nhập.
Điều đó chỉ chứng minh:
Hệ thống biết user là ai.
Nó chưa chứng minh:
User được xem file này.
User được sửa bài này.
User được chấm lớp này.
User được xóa tài khoản này.
User được xem dữ liệu tenant này.
Thông điệp chính của chương:
> Authorization là quyết định một actor có được thực hiện một action trên một resource trong một context cụ thể hay không.
Hay viết ngắn:
Ai được làm gì với cái gì, trong hoàn cảnh nào?
---
37.1. Ví dụ dễ hiểu: tòa nhà và phòng ban
Chương trước ta dùng ví dụ tòa nhà.
Authentication là bảo vệ xác nhận:
Bạn đúng là nhân viên công ty.
Nhưng sau đó còn nhiều câu hỏi:
Bạn có được vào tầng 5 không?
Bạn có được vào phòng server không?
Bạn có được mở két không?
Bạn có được xem hồ sơ nhân sự không?
Bạn có được ký hợp đồng không?
Đó là authorization.
Một người có thẻ nhân viên không có nghĩa là được vào mọi phòng.
Một user đã login không có nghĩa là được xem mọi dữ liệu.
---
37.2. Công thức căn bản của authorization
Một quyết định phân quyền thường có dạng:
Can actor do action on resource in context?
Ví dụ:
Can user_9 view submission_123 in course_3?
Trong đó:
Actor:
user_9
Action:
view
Resource:
submission_123
Context:
course_3
current time
user role
submission owner
assignment status
tenant_id
Đừng chỉ hỏi:
User có role gì?
Hãy hỏi đầy đủ:
User có được làm hành động này trên object cụ thể này không?
---
37.3. Authentication không đủ
Ví dụ API:
GET /submissions/123
Backend thấy request có token hợp lệ:
current_user = user_9
Nếu backend trả submission 123 ngay, hệ thống có thể hở dữ liệu.
Vì cần kiểm tra thêm:
submission_123 thuộc về ai?
user_9 có phải owner không?
user_9 có phải teacher của course chứa submission này không?
user_9 có phải admin có quyền xem không?
tenant có khớp không?
Authentication chỉ mở cửa vào tòa nhà.
Authorization quyết định bạn được vào phòng nào.
---
37.4. Vì sao authorization thường khó hơn login?
Login có flow khá rõ:
Nhập credential.
Verify.
Tạo session/token.
Authorization khó hơn vì nó dính vào nghiệp vụ.
Ví dụ trong AI Judge:
- Student xem bài của mình.
- Teacher xem bài của học sinh trong lớp mình.
- Assistant teacher chỉ được xem, không được sửa điểm.
- Admin trường xem dữ liệu trường mình.
- Platform admin xem được nhiều tenant hơn.
- Assignment đóng thì student không được nộp nữa.
- Teacher được sửa rubric trước khi publish, nhưng không sau khi đã khóa.
- Parent có thể xem kết quả con mình, nhưng không xem bài của học sinh khác.
Đây không phải bài toán kỹ thuật thuần túy.
Nó là luật sản phẩm.
Vì vậy authorization thường phức tạp hơn authentication.
---
37.5. Resource là gì?
Resource là thứ được bảo vệ.
Ví dụ:
- User.
- Course.
- Assignment.
- Submission.
- GradingResult.
- Rubric.
- Feedback.
- File.
- Report.
- Organization.
- Invoice.
- API key.
Không chỉ page mới cần permission.
Object cụ thể cũng cần permission.
Ví dụ:
/submissions/123
không chỉ là một URL.
Nó là resource:
submission_123
Và permission phải gắn với resource đó.
---
37.6. Action là gì?
Action là việc actor muốn làm.
Ví dụ:
- view.
- create.
- update.
- delete.
- submit.
- grade.
- override_score.
- download.
- invite_member.
- publish.
- archive.
- export.
- manage_billing.
Một user có thể được view nhưng không được update.
Một teacher có thể được grade nhưng không được manage_billing.
Một student có thể được submit nhưng không được view submissions của người khác.
Đừng dùng một permission quá rộng kiểu:
can_access_course
nếu thực tế có nhiều hành động khác nhau.
Tốt hơn:
course.view
assignment.create
submission.view
submission.grade
score.override
report.export
---
37.7. Context là gì?
Context là hoàn cảnh ảnh hưởng đến quyết định.
Ví dụ:
User là teacher.
Nhưng teacher của course nào?
Assignment còn mở không?
Submission thuộc tenant nào?
Giờ hiện tại có nằm trong thời gian cho phép không?
Resource có bị khóa không?
User có bị suspended không?
Nếu chỉ nhìn role, ta dễ quyết định sai.
Ví dụ:
Role = teacher
Không có nghĩa là teacher được xem mọi course.
Teacher chỉ được xem course mà họ dạy.
Context làm authorization thực tế hơn.
---
37.8. RBAC là gì?
RBAC là Role-Based Access Control.
Nghĩa là phân quyền dựa trên role.
Ví dụ:
student
teacher
admin
owner
Mỗi role có một tập permission.
Ví dụ:
student:
assignment.view
submission.create
own_submission.view
teacher:
course.view
assignment.create
submission.grade
grading_result.view
admin:
user.manage
course.manage
report.export
RBAC dễ hiểu, dễ bắt đầu.
Nhiều hệ thống nên bắt đầu bằng RBAC.
---
37.9. RBAC mạnh ở đâu?
RBAC mạnh khi tổ chức quyền theo vai trò rõ.
Ví dụ:
- Student.
- Teacher.
- Teaching assistant.
- School admin.
- Platform admin.
Ưu điểm:
- Dễ giải thích.
- Dễ hiển thị trong UI.
- Dễ quản lý.
- Dễ audit.
- Phù hợp app business thông thường.
Ví dụ:
Teacher có thể tạo assignment.
Student có thể nộp bài.
School admin có thể mời teacher.
RBAC là điểm khởi đầu tốt.
---
37.10. RBAC yếu ở đâu?
RBAC yếu khi quyền phụ thuộc vào object và context.
Ví dụ:
Teacher A được xem course 1, không được xem course 2.
Nếu chỉ có role teacher, không đủ.
Bạn cần thêm quan hệ:
teacher_course_membership
Hoặc:
course_members.role = teacher
Ví dụ khác:
Student được xem own submission, không được xem submission của bạn khác.
Role student không đủ.
Cần kiểm tra:
submission.user_id == current_user.id
Vì vậy RBAC thường phải kết hợp object-level checks.
---
37.11. ABAC là gì?
ABAC là Attribute-Based Access Control.
Nghĩa là phân quyền dựa trên thuộc tính.
Thuộc tính có thể thuộc về:
- User.
- Resource.
- Environment.
- Tenant.
- Time.
- Device.
- Region.
Ví dụ:
Cho phép nếu:
user.department == resource.department
và user.level >= 3
và resource.status != locked
Trong AI Judge:
Cho phép student submit nếu:
user.id == enrollment.user_id
và assignment.course_id == enrollment.course_id
và assignment.status == open
và now < assignment.deadline
ABAC linh hoạt hơn RBAC.
Nhưng cũng khó quản lý hơn nếu không có kỷ luật.
---
37.12. ABAC mạnh ở đâu?
ABAC mạnh khi rule phụ thuộc nhiều điều kiện.
Ví dụ:
- Chỉ cho nộp trước deadline.
- Chỉ cho teacher sửa điểm trước khi khóa bảng điểm.
- Chỉ cho user download file nếu file thuộc tenant của họ.
- Chỉ cho admin export report trong organization của mình.
- Chỉ cho support xem dữ liệu khi có ticket active.
ABAC giúp diễn tả:
Không chỉ role, mà cả hoàn cảnh.
Nó rất gần với nghiệp vụ thật.
---
37.13. ABAC yếu ở đâu?
ABAC có thể trở nên khó hiểu.
Nếu rule rải khắp code:
if user.role == ...
if resource.status == ...
if org.plan == ...
if now < ...
Sau vài tháng, không ai biết quyền thật nằm ở đâu.
Vấn đề:
- Khó audit.
- Khó test.
- Khó giải thích cho admin.
- Khó debug vì sao user bị deny.
- Dễ mâu thuẫn rule.
ABAC nên được tổ chức cẩn thận.
Không phải rải điều kiện lung tung ở controller.
---
37.14. ACL là gì?
ACL là Access Control List.
Nó gắn danh sách quyền trực tiếp vào resource.
Ví dụ:
file_123:
user_9: view
user_10: view
user_11: edit
Hoặc:
document_456:
team_1: view
user_7: owner
ACL hợp với các hệ thống chia sẻ object cụ thể:
- Google Drive.
- Notion.
- Document editor.
- Project management.
- File sharing.
ACL trả lời rất trực tiếp:
Ai có quyền gì trên object này?
---
37.15. ACL mạnh và yếu ở đâu?
ACL mạnh khi user chia sẻ từng object cụ thể.
Ví dụ:
Teacher chia sẻ một report cho assistant teacher.
Hoặc:
User mời người khác xem một document.
Nhưng ACL có thể khó quản lý khi số object và rule lớn.
Vấn đề:
- Quyền phân tán trên từng object.
- Khó biết một user có quyền trên bao nhiêu object.
- Khó revoke hàng loạt nếu không thiết kế tốt.
- Kế thừa quyền từ folder/project/team phức tạp.
ACL không xấu.
Nó chỉ hợp với bài toán khác RBAC.
---
37.16. RBAC, ABAC, ACL nên chọn cái nào?
Không cần chọn một cái duy nhất.
Hệ thống thật thường kết hợp.
Ví dụ AI Judge:
RBAC:
student
teacher
school_admin
platform_admin
ABAC:
student chỉ nộp trước deadline
teacher chỉ sửa điểm khi gradebook chưa locked
admin chỉ quản lý tenant của mình
Object-level:
student chỉ xem submission của mình
teacher chỉ xem submission trong course mình dạy
ACL nếu cần:
report/file được chia sẻ riêng cho một người.
Tư duy thực dụng:
> RBAC để quản lý vai trò tổng quát. ABAC để xử lý điều kiện nghiệp vụ. ACL để chia sẻ quyền trên object cụ thể.
---
37.17. Object-level permission là gì?
Object-level permission là kiểm tra quyền trên từng object cụ thể.
Ví dụ:
GET /submissions/123
Không đủ để kiểm tra:
User đã login chưa?
User có role student/teacher không?
Phải kiểm tra:
submission_123 có thuộc user này không?
Hoặc user này có dạy course chứa submission_123 không?
Hoặc user này có quyền admin đúng tenant không?
Rất nhiều lỗi bảo mật nghiêm trọng xảy ra vì thiếu object-level permission.
---
37.18. IDOR là gì?
IDOR là Insecure Direct Object Reference.
Nói dễ hiểu:
User đổi id trên URL và xem được dữ liệu của người khác.
Ví dụ:
/submissions/123
User đổi thành:
/submissions/124
Nếu backend chỉ kiểm tra login mà không kiểm tra ownership/permission, user có thể xem bài người khác.
Đây là lỗi cực phổ biến.
Cách phòng:
Mọi API lấy object theo id phải kiểm tra object-level permission.
Đừng nghĩ dùng UUID là đủ.
UUID khó đoán hơn, nhưng nếu bị lộ URL thì vẫn hở.
Permission mới là lớp chính.
---
37.19. Multi-tenant permission
Multi-tenant là một hệ thống phục vụ nhiều tổ chức/khách hàng.
Ví dụ:
Trường A.
Trường B.
Trường C.
Mỗi tenant có dữ liệu riêng.
Điều tối quan trọng:
User của tenant A không được thấy dữ liệu tenant B.
Vì vậy gần như mọi resource nên có:
tenant_id
Hoặc quan hệ rõ tới tenant.
Ví dụ:
course.tenant_id
assignment.course_id -> course.tenant_id
submission.assignment_id -> assignment.course_id -> tenant_id
Query phải luôn giới hạn theo tenant phù hợp.
---
37.20. Tenant boundary phải nằm ở backend
Không được tin frontend.
Frontend có thể gửi:
tenant_id = school_A
Nhưng backend phải tự kiểm tra:
current_user có thuộc school_A không?
resource có thuộc school_A không?
Sai lầm:
Backend nhận tenant_id từ request và query theo tenant_id đó.
Nếu user đổi tenant_id, có thể xem dữ liệu tenant khác.
Đúng hơn:
Backend lấy tenant từ session/membership/route đã kiểm tra.
Sau đó dùng tenant đó để scope query.
Tenant boundary là luật backend.
Không phải gợi ý từ UI.
---
37.21. Platform admin là ngoại lệ nguy hiểm
Nhiều hệ thống có platform admin.
Ví dụ:
Nhân viên vận hành Synvia có thể hỗ trợ nhiều trường.
Đây là quyền rất mạnh.
Không nên viết kiểu:
if user.is_admin:
allow everything
Vì admin cũng cần giới hạn:
- Admin loại nào?
- Có cần reason/ticket không?
- Có audit log không?
- Có được xem dữ liệu học sinh không?
- Có được sửa điểm không?
- Có được export không?
- Có cần MFA không?
Super admin là nguồn rủi ro lớn.
Quyền càng cao, audit và kiểm soát càng cần chặt.
---
37.22. Permission ở UI không đủ
Ẩn nút trên UI là tốt cho trải nghiệm.
Ví dụ:
Student không thấy nút "Override score".
Nhưng backend vẫn phải kiểm tra.
Vì user có thể:
- Gọi API trực tiếp.
- Sửa request.
- Dùng script.
- Replay request.
- Bỏ qua frontend.
Quy tắc:
> UI giúp user không bấm nhầm. Backend mới là nơi bảo vệ dữ liệu.
Mọi action nhạy cảm phải kiểm tra permission ở backend.
---
37.23. Permission ở API list và API detail
Không chỉ API detail mới cần permission.
API list cũng cần scope.
Ví dụ:
GET /submissions
Nếu user là student:
Chỉ trả submission của chính user.
Nếu user là teacher:
Chỉ trả submission trong course họ dạy.
Nếu user là school admin:
Chỉ trả dữ liệu tenant của trường họ.
Lỗi thường gặp:
Detail API có kiểm tra quyền.
List API quên filter tenant/owner.
List endpoint hở dữ liệu cũng nguy hiểm như detail endpoint.
---
37.24. Query scope
Một cách tốt là đưa permission vào query scope.
Ví dụ tư duy:
visible_submissions_for(user)
Nếu user là student:
WHERE submissions.user_id = user.id
Nếu user là teacher:
WHERE submissions.course_id IN courses_that_user_teaches
Nếu user là school admin:
WHERE submissions.tenant_id = user.tenant_id
Sau đó list/detail đều dựa trên scope này.
Ví dụ detail:
visible_submissions_for(user)
.where(id = submission_id)
.first_or_404()
Nếu không tìm thấy, trả 404 hoặc 403 tùy chính sách.
Cách này giảm lỗi quên permission.
---
37.25. 403 hay 404?
Khi user không có quyền, trả:
403 Forbidden
hay:
404 Not Found
Tùy tình huống.
403 nói:
Tài nguyên tồn tại, nhưng bạn không có quyền.
404 nói:
Không tìm thấy.
Với dữ liệu nhạy cảm, 404 có thể tránh lộ việc resource tồn tại.
Ví dụ:
Student gọi /submissions/999 của người khác.
Trả 404 có thể hợp lý.
Với admin UI, 403 có thể hữu ích hơn để debug quyền.
Điểm chính:
> Chọn chính sách nhất quán. Đừng để response vô tình tiết lộ dữ liệu nhạy cảm.
---
37.26. Deny by default
Nguyên tắc an toàn:
Mặc định deny.
Chỉ allow khi rule rõ ràng cho phép.
Không nên:
Nếu không match rule nào thì allow.
Vì khi thêm resource/action mới, dễ bị mở quyền ngoài ý muốn.
Tư duy đúng:
Không biết có được không?
=> Không được.
Sau đó thêm allow rule cụ thể.
---
37.27. Permission nên nằm ở đâu trong code?
Không nên rải permission lung tung.
Ví dụ xấu:
Controller A check role.
Service B check tenant.
Serializer C ẩn field.
Frontend D ẩn nút.
Worker E không check.
Sau một thời gian, không ai biết rule thật nằm ở đâu.
Tốt hơn là có nơi rõ ràng:
- Policy class.
- Permission service.
- Domain method.
- Query scope.
- Guard/middleware cho rule chung.
Ví dụ:
SubmissionPolicy.can_view(user, submission)
SubmissionPolicy.can_grade(user, submission)
SubmissionQuery.visible_to(user)
Tên không quan trọng bằng việc:
Rule có chỗ ở rõ.
---
37.28. Middleware không đủ cho mọi permission
Middleware tốt để kiểm tra rule chung:
User phải đăng nhập.
User phải có tenant active.
User phải chưa bị suspended.
Nhưng middleware thường không đủ để kiểm tra object cụ thể.
Ví dụ:
GET /submissions/123
Middleware có thể biết:
current_user
Nhưng thường chưa load:
submission_123
course membership
assignment status
Vì vậy object-level permission thường phải nằm ở service/controller/domain layer sau khi load resource hoặc trong query scope.
Middleware là cửa ngoài.
Policy là cửa từng phòng.
---
37.29. Worker cũng cần permission/context
Nhiều người chỉ nghĩ permission ở API.
Nhưng worker cũng có thể làm việc nhạy cảm.
Ví dụ:
Teacher bấm export report.
Backend enqueue job.
Worker tạo file report.
Worker cần biết:
Job này được tạo bởi ai?
Người đó có quyền export không?
Report thuộc tenant nào?
Thường nên kiểm tra quyền trước khi enqueue job.
Nhưng worker cũng nên có đủ context để không xử lý nhầm tenant/resource.
Ví dụ job payload nên có:
requested_by_user_id
tenant_id
course_id
report_type
Và worker query dữ liệu theo tenant/course đã xác thực, không dùng tham số trôi nổi một cách mù quáng.
---
37.30. Permission với file download
File là nơi permission hay bị hở.
Ví dụ:
GET /files/123/download
Backend phải kiểm tra:
- File tồn tại không?
- File thuộc tenant nào?
- File thuộc resource nào?
- User có quyền xem resource đó không?
- File đã available chưa?
- File có private không?
Sau đó mới cấp signed URL.
Không nên:
Ai có file_id cũng lấy được signed URL.
Với AI Judge:
Student chỉ được tải bài nộp/feedback của mình.
Teacher chỉ tải file trong course mình dạy.
Admin chỉ trong tenant của mình.
Object storage private chỉ an toàn nếu backend cấp link đúng quyền.
---
37.31. Permission với search
Search cũng cần permission.
Nếu search index chứa feedback/submission private, query search phải filter theo quyền.
Ví dụ:
Student search feedback
-> chỉ search feedback của chính student.
Teacher:
-> search feedback/submission trong course mình dạy.
School admin:
-> search trong tenant của mình.
Sai lầm:
Đưa mọi document vào search index rồi search không filter permission.
Search result có thể rò rỉ title, snippet, metadata, hoặc id.
Vì vậy search index private cần thiết kế cùng authorization.
---
37.32. Permission với cache
Cache cũng có thể làm hở dữ liệu.
Ví dụ xấu:
cache key = "dashboard:course_12"
Nhưng dashboard khác nhau theo user role.
Student, teacher, admin có thể thấy khác nhau.
Nếu cache không phân biệt user/role/tenant, user này có thể thấy dữ liệu cache của user khác.
Cache key cần chứa boundary quan trọng:
tenant_id
user_id nếu dữ liệu riêng
role/scope nếu output khác nhau
permission version nếu cần
Không phải dữ liệu nào cũng cache giống nhau.
Cache private phải rất cẩn thận.
---
37.33. Permission với analytics
Dashboard analytics cũng phải phân quyền.
Ví dụ:
Teacher xem thống kê course mình dạy.
School admin xem thống kê toàn trường.
Platform admin xem nhiều tenant.
Nếu warehouse chứa dữ liệu nhiều tenant, BI/dashboard phải filter tenant chặt.
Sai lầm:
Production app phân quyền tốt,
nhưng dashboard BI cho xem dữ liệu toàn bộ tenant.
Analytics không nằm ngoài authorization.
Dữ liệu tổng hợp vẫn có thể nhạy cảm.
---
37.34. Permission thay đổi theo thời gian
Quyền không cố định.
Ví dụ:
- Teacher bị gỡ khỏi course.
- Student chuyển lớp.
- Assignment hết hạn.
- Gradebook bị khóa.
- User bị suspend.
- Tenant hết gói.
- Admin bị revoke.
Nếu quyền được cache hoặc nhét vào token quá lâu, hệ thống có thể dùng quyền cũ.
Ví dụ:
JWT chứa role=teacher sống 24 giờ.
Teacher đã bị gỡ quyền.
Token vẫn còn role cũ.
Cần cân nhắc:
- Token ngắn hạn.
- Permission version.
- Server-side check cho action nhạy cảm.
- Revoke session.
- Cache TTL ngắn.
Permission càng quan trọng, càng không nên phụ thuộc vào dữ liệu quá cũ.
---
37.35. Có nên nhét permission vào JWT không?
Có thể nhét một ít thông tin vào JWT:
sub
tenant_id hiện tại
role tổng quát
scope cơ bản
Nhưng không nên nhét quá nhiều quyền chi tiết.
Vì:
- Token có thể lỗi thời.
- Token phình to.
- Revoke khó.
- Rule nghiệp vụ thay đổi không phản ánh ngay.
- Object-level permission không thể nhét hết.
Ví dụ không nên:
JWT chứa danh sách 10.000 course_id user được xem.
Tốt hơn:
JWT xác nhận user.
Backend kiểm tra membership/permission khi cần.
Token có thể giúp tối ưu.
Nhưng source of truth của permission quan trọng nên nằm ở backend/database/policy.
---
37.36. Role explosion
Role explosion là khi hệ thống tạo quá nhiều role để xử lý mọi biến thể.
Ví dụ:
teacher
teacher_readonly
teacher_grader
teacher_grader_no_export
teacher_course_admin
teacher_course_admin_no_billing
assistant_teacher_readonly_export
Sau một thời gian, không ai hiểu role nào làm gì.
Nếu role bắt đầu nổ, có thể cần:
- Role ít hơn.
- Permission riêng.
- Policy theo context.
- Custom role nếu sản phẩm cần.
- ABAC rule.
Role nên đại diện cho vai trò dễ hiểu.
Đừng biến role thành mọi tổ hợp permission.
---
37.37. Permission matrix
Permission matrix là bảng mô tả role nào có action nào.
Ví dụ:
| Action | Student | Teacher | School Admin | |---|---:|---:|---:| | assignment.view | Có | Có | Có | | assignment.create | Không | Có | Có | | submission.create | Có | Không | Không | | submission.grade | Không | Có | Có | | score.override | Không | Có điều kiện | Có điều kiện | | report.export | Không | Có điều kiện | Có |
Bảng này giúp:
- Product hiểu quyền.
- Developer code đúng.
- Tester viết test.
- Support giải thích cho user.
Nhưng matrix chỉ là khởi đầu.
Vì nhiều ô "Có điều kiện" cần policy chi tiết.
---
37.38. Policy nên trả lời được "vì sao bị từ chối"
Trong UI/admin, rất hữu ích nếu hệ thống biết vì sao deny.
Ví dụ:
Bạn không thể sửa điểm vì gradebook đã locked.
Hoặc:
Bạn không thuộc course này.
Nhưng cẩn thận không tiết lộ thông tin nhạy cảm cho user không có quyền.
Ví dụ public API có thể chỉ trả:
Not found.
Internal/admin UI có thể hiển thị lý do chi tiết hơn.
Policy có thể trả:
allow/deny + reason
Điều này giúp debug và trải nghiệm tốt hơn.
---
37.39. Test authorization
Authorization rất cần test.
Không chỉ test happy path.
Phải test:
- Student không xem submission của student khác.
- Teacher không xem course không dạy.
- School admin không xem tenant khác.
- Assistant teacher không override score nếu không có quyền.
- User bị suspend không thực hiện action.
- Assignment đóng thì student không submit.
- File private không download được nếu không có quyền.
- Search không trả kết quả ngoài scope.
- List API không leak dữ liệu.
Một bug authorization có thể là data breach.
Test phân quyền là test bảo mật, không phải test phụ.
---
37.40. Permission trong database
Một số bảng thường gặp:
users
organizations
memberships
roles
permissions
role_permissions
course_members
resource_acl
Ví dụ đơn giản:
course_members
--------------
course_id
user_id
role
Role trong course:
student
teacher
assistant
Khi kiểm tra:
User có role teacher trong course chứa assignment này không?
Thiết kế database permission nên phản ánh domain.
Đừng chỉ có một cột is_admin rồi cố gắng dùng cho mọi thứ.
---
37.41. is_admin là cái bẫy
Ban đầu, app có:
is_admin = true/false
Rất tiện.
Sau đó cần:
- School admin.
- Course admin.
- Billing admin.
- Readonly admin.
- Support admin.
- Platform admin.
Cột is_admin bắt đầu không đủ.
Code mọc nhiều điều kiện:
if is_admin or is_teacher or is_owner...
Không phải lúc nào is_admin cũng sai.
App nhỏ có thể dùng.
Nhưng hãy biết nó sẽ vỡ khi quyền phong phú hơn.
Với hệ thống multi-tenant, is_admin toàn cục đặc biệt nguy hiểm.
Hãy hỏi:
Admin của cái gì?
Admin trong tenant nào?
Admin được làm action nào?
---
37.42. Ownership
Ownership là quyền do user sở hữu resource.
Ví dụ:
submission.user_id == current_user.id
Student được xem bài của mình.
User được sửa profile của mình.
Owner organization được quản lý organization.
Ownership là rule rất phổ biến.
Nhưng cũng cần giới hạn.
Ví dụ:
Student sở hữu submission nhưng không được sửa sau deadline.
Hoặc:
User sở hữu account nhưng không được tự nâng mình thành admin.
Ownership là một phần của policy, không phải vé toàn quyền.
---
37.43. Delegation
Delegation là ủy quyền.
Ví dụ:
Teacher A cho assistant teacher B chấm một assignment.
Hoặc:
School owner giao billing cho kế toán.
Delegation cần trả lời:
- Ai ủy quyền?
- Ủy quyền cho ai?
- Quyền gì?
- Trên resource nào?
- Có hết hạn không?
- Có revoke được không?
- Có audit không?
Nếu hệ thống cần delegation, ACL hoặc permission grant table có thể phù hợp.
Ví dụ:
permission_grants
-----------------
granted_by
granted_to
resource_type
resource_id
permission
expires_at
revoked_at
Đừng chỉ thêm role mới nếu bản chất là quyền được ủy quyền trên object cụ thể.
---
37.44. Temporary permission
Một số quyền chỉ nên tồn tại tạm thời.
Ví dụ:
- Link mời vào lớp.
- Support access trong 24 giờ.
- Temporary file download URL.
- Teacher mở lại assignment trong 1 ngày.
- Parent access theo năm học.
Temporary permission cần:
- expires_at.
- revoked_at.
- audit.
- scope rõ.
Nếu không có hết hạn, quyền tạm thường trở thành quyền vĩnh viễn.
Đây là nguồn rủi ro rất thực tế.
---
37.45. Least privilege
Least privilege nghĩa là:
Chỉ cấp quyền vừa đủ để làm việc cần làm.
Không cấp rộng vì tiện.
Ví dụ:
Worker tạo report không cần quyền xóa user.
Assistant teacher chấm bài không cần quyền quản lý billing.
Support staff không cần xem toàn bộ dữ liệu nếu chỉ hỗ trợ một ticket.
Least privilege giúp giảm thiệt hại khi account/service bị lộ.
---
37.46. Separation of duties
Separation of duties nghĩa là chia trách nhiệm để một người không làm hết mọi bước nhạy cảm.
Ví dụ:
Một người tạo payout, người khác approve.
Trong AI Judge có thể chưa cần nhiều.
Nhưng với hành động nhạy cảm:
- Xóa dữ liệu hàng loạt.
- Export dữ liệu học sinh.
- Thay đổi rubric sau khi đã chấm.
- Sửa điểm hàng loạt.
- Thay đổi billing.
có thể cần:
- Confirmation.
- Approval.
- Audit log.
- Re-auth.
Không phải app nào cũng cần phức tạp.
Nhưng phải biết mẫu này tồn tại.
---
37.47. Audit log cho authorization
Hành động nhạy cảm nên có audit log.
Ví dụ:
teacher_score_overridden
rubric_changed
student_data_exported
permission_granted
permission_revoked
admin_accessed_student_record
Audit log nên ghi:
- Ai làm.
- Làm gì.
- Resource nào.
- Tenant nào.
- Khi nào.
- IP/device nếu cần.
- Trước/sau nếu phù hợp.
- Lý do nếu có.
Audit log không ngăn lỗi.
Nhưng giúp điều tra và tạo trách nhiệm.
Với quyền cao, audit log gần như bắt buộc.
---
37.48. Authorization trong microservices
Trong microservices, authorization khó hơn.
Ví dụ:
API Gateway nhận request.
Submission Service giữ submission.
Course Service giữ membership.
File Service giữ file.
Grading Service giữ result.
Service nào quyết định quyền?
Có vài cách:
1. Gateway kiểm tra auth chung, service kiểm tra object-level permission. 2. Service gọi permission service. 3. Token chứa scope cơ bản, service kiểm tra thêm dữ liệu domain. 4. Domain owner service quyết định quyền cho resource của mình.
Không nên nghĩ:
Gateway đã xác thực token nên service không cần kiểm tra gì nữa.
Gateway thường biết user là ai.
Service/domain mới biết resource thuộc về ai và rule nghiệp vụ là gì.
---
37.49. Centralized permission service
Một permission service trung tâm có thể hữu ích khi:
- Nhiều service cần rule giống nhau.
- Permission rất phức tạp.
- Cần audit/decision log tập trung.
- Cần policy language.
- Cần quản trị quyền động.
Nhưng nó cũng có rủi ro:
- Trở thành điểm nghẽn.
- Latency tăng.
- Rule xa domain.
- Nếu service chết, nhiều thứ bị ảnh hưởng.
- Debug phức tạp.
Không nên tạo permission service quá sớm.
Với monolith/modular monolith, policy trong code/domain thường đơn giản hơn.
Khi microservices thật sự lớn, permission service mới đáng cân nhắc.
---
37.50. Policy engine là gì?
Policy engine là công cụ chuyên đánh giá rule phân quyền.
Ví dụ:
- OPA.
- Cedar.
- Casbin.
- Zanzibar-style systems.
Chúng giúp diễn tả rule ngoài business code hoặc theo mô hình riêng.
Rất hữu ích với hệ thống lớn, quyền phức tạp, nhiều service.
Nhưng nếu app chưa có nhu cầu đó, policy engine có thể làm mọi thứ khó hơn.
Trước khi dùng, hỏi:
- Rule có thật sự phức tạp chưa?
- Team có hiểu mô hình không?
- Debug decision thế nào?
- Latency thế nào?
- Dữ liệu context lấy ở đâu?
- Ai quản lý policy?
Tool mạnh không thay thế được việc hiểu quyền nghiệp vụ.
---
37.51. Authorization và external service
Khi gọi service bên ngoài, đừng gửi quá nhiều quyền.
Ví dụ:
AI service chỉ cần nội dung cần chấm và rubric.
Nó không cần:
- Toàn bộ profile học sinh.
- Dữ liệu tenant không liên quan.
- Token user thật.
- Quyền admin.
Khi service-to-service, nên dùng service credential riêng và scope hẹp.
Chương sau sẽ nói kỹ hơn.
Ở đây chỉ cần nhớ:
> Đừng truyền quyền người dùng hoặc dữ liệu nhạy cảm sang nơi không cần.
---
37.52. Permission không chỉ là bảo mật, còn là UX
Nếu user không có quyền, UI nên phản ứng rõ.
Ví dụ:
Nút bị ẩn nếu chắc chắn không có quyền.
Nút bị disabled nếu muốn cho biết tính năng tồn tại nhưng chưa đủ điều kiện.
Thông báo nói lý do nếu an toàn.
Ví dụ:
Bạn không thể nộp bài vì assignment đã đóng.
Tốt hơn:
Forbidden.
Nhưng với dữ liệu nhạy cảm, không nên nói quá nhiều.
Authorization tốt vừa bảo vệ dữ liệu, vừa giúp user hiểu mình nên làm gì tiếp theo.
---
37.53. Cách nghĩ trước khi code permission
Trước khi code, viết ra:
Resource là gì?
Action là gì?
Actor là ai?
Context nào ảnh hưởng?
Tenant boundary ở đâu?
Ai là owner?
Role nào có quyền mặc định?
Điều kiện nào làm quyền bị mất?
Quyền có hết hạn không?
Có cần audit không?
Ví dụ với Submission:
Resource:
Submission
Actions:
view
create
update
delete
grade
download_file
Rules:
Student view own submission.
Student create submission nếu assignment open và enrolled.
Student update own draft trước deadline.
Teacher view/grade submissions trong course mình dạy.
School admin view submissions trong tenant mình.
Platform admin chỉ xem khi có support reason và audit.
Viết rule trước giúp code bớt đoán.
---
37.54. Bảng chọn nhanh
| Nhu cầu | Cách nghĩ thường hợp | |---|---| | Vai trò rõ như student/teacher/admin | RBAC | | Quyền phụ thuộc deadline/status/tenant | ABAC/policy theo context | | Chia sẻ từng file/document cụ thể | ACL | | User chỉ xem dữ liệu của mình | Ownership check | | SaaS nhiều trường/công ty | Tenant scoping bắt buộc | | API lấy object theo id | Object-level permission | | Search dữ liệu private | Filter permission trong search | | Cache dữ liệu private | Cache key theo user/tenant/scope | | Dashboard nhiều tenant | Filter tenant trong warehouse/BI | | Quyền cao như platform admin | MFA + audit + least privilege | | Permission quá phức tạp nhiều service | Cân nhắc permission service/policy engine |
---
37.55. Tóm tắt bằng AI Judge
Trong AI Judge, authentication cho ta:
current_user = user_9
Authorization phải quyết định:
user_9 có được xem assignment_12 không?
user_9 có được nộp submission cho assignment_12 không?
user_9 có được xem submission_123 không?
user_9 có được chấm submission_123 không?
user_9 có được override score không?
user_9 có được download report không?
user_9 có được export dữ liệu lớp không?
Một thiết kế thực dụng:
RBAC:
student, teacher, school_admin, platform_admin
Membership:
user thuộc course/tenant nào
Object-level policy:
submission/file/result thuộc ai/course/tenant nào
ABAC:
deadline, status, locked gradebook, suspended user
Audit:
override score, export report, admin access
Điểm quan trọng:
Đã login không có nghĩa là có quyền.
Có role teacher không có nghĩa là được xem mọi lớp.
Có file_id không có nghĩa là được tải file.
Có tenant_id từ request không có nghĩa là được tin tenant_id đó.
---
37.56. Kết luận của chương
Authorization là phần dễ bị đánh giá thấp.
Nó không chỉ là:
if user.is_admin
Nó là toàn bộ hệ thống quyết định:
Ai được làm gì với resource nào trong context nào.
RBAC giúp quản lý role tổng quát.
ABAC giúp diễn tả điều kiện nghiệp vụ.
ACL giúp chia sẻ quyền trên object cụ thể.
Object-level permission giúp tránh lỗi đổi id xem dữ liệu người khác.
Multi-tenant scoping giúp tenant này không thấy dữ liệu tenant khác.
Thông điệp cần nhớ:
> UI có thể ẩn nút, nhưng backend phải bảo vệ dữ liệu. Permission phải được kiểm tra ở nơi dữ liệu/hành động thật sự xảy ra.
Ở chương tiếp theo, ta sẽ nói về service-to-service security: service gọi service có cần xác thực không, API key/JWT/mTLS khác nhau ra sao, vì sao internal network không có nghĩa là an toàn, và secret/key rotation nên được nghĩ thế nào.