Một login screen đẹp có thể che một sự thật rất xấu: app không có auth thật.
Trong vibe coding, agent rất dễ dựng màn hình đăng nhập, route dashboard, avatar user, role badge, nút logout. Nhìn như SaaS. Nhưng nếu backend không kiểm quyền, database không có policy, API không check ownership, thì login screen chỉ là cửa giả.
Auth bypass là lỗi có thể biến app vibe-coded thành public database. Không cần exploit phức tạp. Chỉ cần đổi URL, gọi API trực tiếp, dùng incognito, hoặc sửa một ID trong request.
Authentication khác authorization
Hai từ này hay bị trộn:
- Authentication: bạn là ai?
- Authorization: bạn được làm gì?
Một app có thể biết bạn là user A nhưng vẫn cho bạn xem data của user B. Khi đó authentication có thể đúng, authorization vẫn hỏng.
Ví dụ:
/api/bookings/123
Nếu user A đổi 123 thành booking của user B và vẫn xem được, đó là broken access control. UI không hiện link không đủ. Backend phải chặn.
UI guard không phải security
AI hay làm security ở UI trước:
- Nếu user không phải admin thì ẩn button delete.
- Nếu chưa login thì redirect khỏi page.
- Nếu role không đúng thì không render tab settings.
Những thứ này tốt cho UX, nhưng không đủ cho security. User có thể gọi API trực tiếp. Browser devtools, curl, hoặc một script nhỏ đều có thể bypass UI.
Security thật phải nằm ở server/API/database:
- API check user identity.
- API check ownership hoặc role.
- Database policy chặn row không thuộc user.
- Mutating action kiểm lại permission.
- File storage kiểm access trước khi download.
Nếu permission chỉ nằm ở React component, đó là trang trí.
Vibe-coded app hay sai ở object ownership
Với app nhỏ, agent thường tạo data model đơn giản:
Booking has id, customerName, date, status.
Nhưng thiếu:
Booking belongs to user/team/tenant.
Không có ownership thì không có cách đúng để chặn user khác xem booking. App single-user demo thì ổn. App multi-user thật thì nguy hiểm.
Prompt nên nói rõ:
Every record must have an owner or tenant boundary.
Do not rely on UI hiding for permission.
Explain where authorization is enforced for each API route.
Non-tech vẫn test được auth bypass
Bạn không cần biết backend code để bắt nhiều lỗi auth.
Test tối thiểu:
- Tạo account A, tạo một record.
- Copy URL record đó.
- Logout.
- Mở incognito, paste URL.
- Login account B, paste URL.
- Thử đổi ID trong URL.
- Thử bấm action bị ẩn bằng direct API nếu agent có cung cấp route.
- Kiểm data của account A có xuất hiện trong account B không.
Nếu app không hỗ trợ account thật, ghi rõ “auth not implemented”. Đừng để login UI làm bạn tưởng đã xong.
Database policy là chốt chặn cuối
Với Supabase/Firebase-style app, database policy rất quan trọng. Frontend có thể dùng public client key hợp lệ, nhưng database vẫn phải có rule/RLS để chỉ trả row đúng user.
Nguy hiểm không phải lúc nào cũng là “key public”. Nhiều platform được thiết kế để frontend có public key. Nguy hiểm là public key trỏ tới database không có policy.
Câu hỏi nên hỏi agent:
For each table with user data:
- Is row-level access enforced?
- Which field represents owner or tenant?
- Can anonymous users read it?
- Can user A update user B's row?
Nếu agent trả lời chung chung, cần dev/security review.
Prompt audit auth
Sau khi app có login, dùng prompt:
Do not edit code.
Audit authentication and authorization.
For each route, API endpoint, database table, and storage bucket:
- Who can read?
- Who can create?
- Who can update?
- Who can delete?
- Where is this enforced: UI, API, database, or storage policy?
Mark anything enforced only in UI as unsafe.
Prompt này ép agent nói ra layer enforcement. Nếu câu trả lời toàn UI, bạn đã tìm thấy vấn đề.
Khi nào phải dừng
Dừng và gọi dev review nếu app có:
- Customer data.
- Payment.
- Medical, school, employee, finance data.
- Multi-tenant workspace.
- Admin role.
- File upload riêng tư.
- Public sharing link.
- API key hoặc service role.
Những vùng này không nên được bảo vệ bằng “agent nói secure rồi”.
Chốt lại
Auth bypass là lỗi khó nhìn bằng mắt nhưng hậu quả rất thật. App có login screen chưa chắc có auth. App biết user là ai chưa chắc chặn đúng quyền. UI ẩn button chưa chắc API an toàn.
Vibe coding có kiểm soát cần một rule đơn giản: permission phải được enforce ở backend hoặc database, không chỉ ở giao diện.
Nếu non-tech chỉ nhớ một test, hãy nhớ test account A/account B. Tạo data bằng account A, thử đọc bằng account B. Nếu đọc được, đừng polish UI nữa. Bạn đang có public database mặc áo app.