Agent gọi API thay người dùng: dùng ủy quyền, đừng dùng service account chung
Lối tắt "cấp cho agent một tài khoản admin" giúp bản demo chạy ngay trong chiều nay, nhưng khi có sự cố, nó không trả lời được câu hỏi đầu tiên mà đội bảo mật sẽ hỏi.
Tóm tắt nhanh
- Khi mọi người dùng chung một service account hay một API key, dấu vết bị mất: không ai biết người dùng nào đã khiến agent hành động.
- Hãy dùng delegation. Token mới mang danh tính người dùng, claim act ghi lại agent, và quyền không bao giờ vượt quá quyền của người dùng.
- Đừng chuyển tiếp nguyên token của client xuống API phía sau. Hãy kiểm tra audience, đổi sang token mới, xin scope hẹp và trả yêu cầu MFA về cho client.
Tuần thứ hai ở chỗ khách hàng, agent hỗ trợ nội bộ của bạn đã đọc được ticket và cập nhật được đơn hàng. Để kịp demo hôm thứ Sáu, một kỹ sư bên khách đề xuất cấp cho agent một service account có quyền admin trên hệ thống ticket.
Đề xuất này nghe rất hợp lý. Nhưng chính ở chỗ này, nhiều FDE nhận về một khoản nợ bảo mật mà sau này rất khó trả.
Vấn đề nằm ở log. Ba tháng sau, nếu một ticket bị đóng nhầm hoặc dữ liệu khách hàng bị đọc trái phép, đội bảo mật sẽ hỏi ai đã làm việc đó. Với một credential dùng chung, bạn không trả lời được.
Auth0 mô tả đúng tình huống này khi bàn về API key tĩnh dùng chung cho agent: bạn không có cách nào biết người dùng nào, hay hành động nào của agent, đã dùng đến key.
Theo Auth0, thiếu dấu vết kiểm toán như vậy là điều không thể chấp nhận về mặt bảo mật, tuân thủ và xử lý sự cố. Nếu muốn agent của mình qua được vòng review bảo mật ở một doanh nghiệp lớn, bạn cần nắm chắc kỹ năng trong bài này.
Agent nên trở thành người dùng hay đi bên cạnh họ?
RFC 8693 định nghĩa OAuth 2.0 Token Exchange, giao thức dùng để xin token bảo mật từ authorization server. Giao thức này hỗ trợ cả impersonation (mạo danh) lẫn delegation (ủy quyền). Hai kiểu nghe na ná nhau nhưng để lại log rất khác nhau.
Về impersonation, RFC viết rằng khi A mạo danh B thì trong phạm vi quyền của token, A chính là B. API phía sau chỉ thấy B, còn dấu vết của bên thực sự hành động thì biến mất. Với delegation, A vẫn giữ danh tính riêng, tách khỏi B. Một agent hành động thay người dùng nên chọn kiểu thứ hai.
Impersonation
- Token chỉ mang danh tính người dùng
- API phía sau không phân biệt được agent với người dùng
- Log ghi là người dùng làm, dù thực ra agent làm
Delegation
- Token mang danh tính của cả người dùng lẫn agent
- Claim act ghi lại bên đang hành động
- Log cho biết ai yêu cầu và ai thực hiện
Công cụ để ghi lại delegation là claim act (actor) trong JWT. RFC 8693 tạo ra claim này để ghi rõ rằng token được cấp theo kiểu ủy quyền, và bên đang hành động là ai.
Một luồng hoàn chỉnh, từng bước
Thử hình dung một nhân viên hỗ trợ tên Lan nhờ agent tóm tắt các ticket đang mở. Ứng dụng chat gửi cho agent token của Lan, và audience của token này là chính agent. Agent không cầm token đó đi gọi hệ thống ticket. Nó gửi request đổi token tới authorization server, với grant_type là giá trị riêng do RFC 8693 quy định:
POST /token
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=<token Lan gửi cho agent>
&actor_token=<token định danh của chính agent>
&scope=tickets.read
Nếu request được chấp nhận, payload của token mới sẽ trông gần giống thế này. Lan vẫn là chủ thể, agent nằm trong act, và scope chỉ có quyền đọc:
{
"sub": "[email protected]",
"scope": "tickets.read",
"act": { "sub": "support-agent" }
}
Nhờ vậy, mỗi dòng log của hệ thống ticket trả lời được hai câu hỏi: ai yêu cầu và ai thực hiện. Đây chính là thứ một service account dùng chung không làm được.
On-behalf-of: ba ràng buộc FDE nên thuộc lòng
Nếu khách hàng dùng Microsoft Entra, phiên bản cụ thể của ý tưởng này là luồng On-Behalf-Of (OBO). Microsoft mô tả OBO chính là delegation trong OAuth, nhằm chuyển danh tính và quyền của người dùng qua cả chuỗi request. Tài liệu của họ nêu ba ràng buộc mà FDE nên nhớ.
Ràng buộc đầu tiên: OBO chỉ dùng delegated scope, không dùng application role. Vì vậy tầng giữa không thể trao cho người dùng nhiều quyền hơn những gì họ vốn có. Nếu Lan không được xóa ticket thì agent làm việc thay Lan cũng không xóa được, kể cả khi bị prompt injection dụ làm vậy.
Ràng buộc thứ hai: một ứng dụng không được đem token cấp cho ứng dụng khác đi đổi. Audience của token đầu vào phải là chính agent. Token cấp cho một API khác thì phải bị từ chối, không được chuyển tiếp.
Ràng buộc thứ ba là cái hay bị bỏ qua nhất. Khi API phía sau đòi Conditional Access, chẳng hạn MFA, tầng giữa không tự đáp ứng được. Nó phải trả lỗi về client để client đưa người dùng qua bước xác thực.
Đây cũng là lý do rõ nhất vì sao danh tính của một service không thay được người dùng: service không có điện thoại để bấm xác nhận MFA.
Ba lỗi hay gặp ở chỗ khách hàng
Lỗi đầu tiên là token passthrough: nhận token của client rồi chuyển nguyên xuống API phía sau. Hướng dẫn bảo mật của Model Context Protocol gọi đây là anti-pattern, và đặc tả authorization cấm hẳn cách làm này.
Lý do là log của API phía sau sẽ ghi request như thể nó đến từ một nguồn khác, mang một danh tính khác, chứ không phải từ server đã thực sự chuyển tiếp nó.
Lỗi thứ hai là xin sẵn mọi scope ngay từ đầu “cho đỡ phải hỏi lại”. Tài liệu MCP cảnh báo rằng scope thiết kế kém khiến thiệt hại lớn hơn khi token bị lộ, làm người dùng khó chịu hơn và làm dấu vết kiểm toán mờ đi.
Với tác vụ tóm tắt, bạn chỉ nên xin tickets.read, và chỉ xin quyền ghi khi người dùng thật sự yêu cầu cập nhật.
Lỗi thứ ba là để agent tự giữ refresh token của từng nhà cung cấp. Mẫu Token Vault của Auth0 giải quyết chỗ này: agent chỉ giữ token Auth0 và đổi chúng lấy token của nhà cung cấp khi cần, chứ không bao giờ chạm vào refresh token của nhà cung cấp.
Nếu identity provider của khách hàng đã hỗ trợ cách làm tương tự, hãy dùng nó thay vì tự viết một kho lưu token.
Tự làm ở dự án tiếp theo
Bắt đầu bằng việc vẽ chuỗi gọi: người dùng, client, agent, rồi từng API phía sau. Trên mỗi mũi tên, ghi rõ token nào đang đi qua và audience của nó là gì. Bản vẽ này thường cho thấy ngay chỗ có người định chuyển tiếp token hoặc dùng chung một key.
Tiếp theo, ở agent, hãy kiểm tra audience của mọi token đầu vào trước khi làm bất cứ việc gì khác. Đổi token bằng token exchange hoặc OBO, xin scope hẹp nhất đủ cho tác vụ, và trả nguyên lỗi Conditional Access về client thay vì nuốt lỗi rồi thử lại bằng một credential khác.
Cuối cùng, cùng đội bảo mật của khách đọc một dòng log thật ở API phía sau, xem nó có cho thấy cả người dùng lẫn agent hay không.
Về nghề nghiệp, đây là kỹ năng rất dễ chứng minh. Khi đọc JD của FDE, hãy để ý xem công ty có nhắc đến OAuth, identity hay tích hợp với hệ thống doanh nghiệp không. Nếu có, hãy đưa kinh nghiệm này lên đầu.
Trong CV, đừng chỉ ghi “có kinh nghiệm OAuth”. Hãy mô tả việc bạn đã thiết kế luồng ủy quyền giúp mỗi lệnh gọi API của agent truy được về đúng người dùng.
Một câu như vậy cho người phỏng vấn thấy bạn từng làm ra hệ thống có thể qua được một vòng review bảo mật thật.
Bài tập tuần này: lấy một agent bạn đã viết, giải mã token nó dùng để gọi API phía sau, rồi tự hỏi nếu ngày mai có sự cố, token này có cho bạn biết ai đã yêu cầu agent hành động không. Nếu câu trả lời là không, bạn đã biết việc cần sửa trước buổi demo tiếp theo.
Bài này có hữu ích không?
Cảm ơn bạn đã góp ý!
6 nguồn
- RFC 8693 - OAuth 2.0 Token Exchange · 2020-01
- RFC 8693 - OAuth 2.0 Token Exchange · 2020-01
- Microsoft identity platform and OAuth2.0 On-Behalf-Of flow - Microsoft identity platform | Microsoft Learn · 2025-01-04
- Security Best Practices - Model Context Protocol
- API Keys and AI Agents: Four Common Risks · 2025-09-02
- Auth0 Token Vault: Secure Token Exchange for AI Agents · 2025-10-09