Ở bài Web Sign-In with OpenID Connect chúng ta đã tìm hiểu về kịch bản sử dụng OIDC để xử lý đăng nhập. Trong bài này hãy tìm hiểu nốt một nửa còn lại liên quan đến việc uỷ quyền và gọi APIs.
Đây là kịch bản chủ yếu mà OAuth2 xử lý, hầu hết các cuộc thảo luận sẽ tập trung vào grant điển hình mà OAuth 2.0 cung cấp để xử lý kịch bản truy cập API ủy quyền, đó là Authorization Code grant.
The Authorization Code Grant
Ở mức high level, cách chúng ta gọi APIs từ ứng dụng web sẽ gần giống nhau với bất kỳ clients nào. Tuy nhiên ở mức độ chi tiết hơn, tùy thuộc vào loại client, chúng ta sẽ sử dụng các grants khác nhau, với các thuộc tính khác nhau.
Trong bài này, chúng ta muốn tập trung vào các kịch bản trong đó một ứng dụng web gọi API từ mã phía server của nó. Authorization code grant, cho phép một ứng dụng web truy cập một API thay mặt người dùng và trong scope mà người dùng cho phép.
Lưu ý rằng authorization code grant là một luồng ủy quyền. Nó cho phép các clients làm những việc thay mặt người dùng, có nghĩa là khả năng của người dùng là giới hạn cứng cho những gì một ứng dụng có thể làm. Nói cách khác, một client nhận được token thông qua code grant không thể làm nhiều hơn những gì người dùng có thể làm.
Calling an API from a Web App
Trong bài Web Sign-In with OpenID Connect, chúng ta đã khám phá cách thực hiện đăng nhập web thông qua front channel, điều này cho phép triển khai toàn bộ kịch bản mà không cần bất kỳ secret nào.
Tuy nhiên, trong authorization code grant, việc sử dụng một thông tin xác thực của ứng dụng như client secret là điều không thể tránh khỏi. Bất cứ khi nào ứng dụng web đổi một mã ủy quyền, nó cần phải xác thực như một client với authorization server.
- Client secret: là thông tin được cấp khi bạn đăng ký client với authorization server, cùng với các thông tin khác như
client_id
,callback_url
, etc.
Giờ hãy đi sâu vào chi tiết của authorization code grant thông qua hình sau đây.
Giống như chúng ta đã làm trong lần giải thích đầu tiên về luồng OAuth2 trong bài Background and Context of Digital Identity, phần Delegated Authorization: OAuth2, ở đây chúng ta giả định rằng người dùng đã đăng nhập vào ứng dụng web.
Chúng ta không cần biết làm thế nào hoạt động đăng nhập đó diễn ra, và chúng ta không quan tâm trong bối cảnh này - hoạt động gọi API có thể được thực hiện độc lập với đăng nhập.
(1): Route Request
Kịch bản lần này sẽ là cho phép người dùng đặt một cuộc hẹn, nhưng chức năng này yêu cầu quyền truy cập APIs để thay mặt người dùng. Do đó để sử dụng chức năng này chúng ta cần thực hiện các bước uỷ quyền cho ứng dụng thay mặt cho chúng ta làm việc này.
Đầu tiên chúng ta sẽ truy cập đường dẫn với chức năng đặt một cuộc hẹn, trong trường hợp này là /bookAppointment
. Nếu ứng dụng chưa được uỷ quyền, nó sẽ phản hồi với mã code HTTP 302 tương đương với redirect request và kèm theo với các thuộc tính để thực hiện authorization request.
(2): Authorization Request
Phản ứng của trình duyệt với response HTTP 302 khá quen thuộc, đó là thực hiện một GET request với các tham số tương ứng. Nhưng trước khi đến các bước tiếp theo, hãy xem xét qua các tham số một chút.
- Đầu tiên chúng ta có
nonce
một tham số để tránh các cuộc tấn công giả mạo. Nó sẽ được trả về từ authorization server bên trong ID Token claims. Chi tiết hơn lý do vì sao chúng ta cần ID token trong luồng uỷ quyền sẽ được giải thích sau. Audience
xác định đối tượng mà token dành cho. Tiếp theo làclient_id
đại diện cho client được đăng ký tại authorization server.Response_type
cho grant cụ thể này làcode
, chúng ta sẽ lấy code từ authorization server, thứ này sẽ giúp chúng ta đổi lấy access_token trong các bước tiếp theo.Scope
ta sẽ thấy các giá trị scope đã gặp trước đóopenid
,profile
,email
, chỉ ra rằng chúng ta cũng muốn lấy ID Token, nhưng lần này ID token sẽ không sử dụng cho mục đích đăng nhập.- Lý do chúng ta muốn lấy ID token ở đây chủ yếu là muốn xác định xem access_token thuộc về ai, user nào. Vì bản chất access_token là opaque đối với client và bạn sẽ không lấy được bất kỳ thông tin nào từ access_token. Và cũng không nên cố gắng đọc nó.
- Nếu không có ID Token chúng ta sẽ chỉ mù quáng sử dụng mà không có các chỉ dẫn về danh tính user.
Một giá trị scope khác là read:appointment
, đại diện cho quyền mà API chúng ta muốn gọi. Bằng cách trình bày giá trị scope này trong yêu cầu uỷ quyền, client đang nói với authorization server là "Ứng dụng muốn thay mặt user thực hiện quyền read:appointment". Đó chính xác là những gì authorization server cần biết.
- Tham số tiếp theo là
redirect URI
, một endpoint dùng để nhận thông tin mà authorization server trả về.
(3): 302 Redirect Execution
Tiếp theo trình duyệt một GET request với các tham số tương ứng đến authorization endpoint.
(4): Authorization Response
Sau khi nhận được yêu cầu ủy quyền, authorization server quyết định những gì cần thiết để xác thực người dùng và đi qua nó. Sau đó nói với user rằng: "Này, client X muốn đọc và đặt các cuộc hẹn thay mặt bạn. Nó có ổn với bạn không?"
Khoảnh khắc mà người dùng đồng ý, authorization server trả về phản hồi với authorization code
được yêu cầu trong query string, theo response_type mà chúng ta đã yêu cầu trước đó.
(5): Providing the Authorization Code to the Web App
Tại thời điểm này công việc đơn giản của trình duyệt là chuyển authorization code đến với ứng dung. Các bước tiếp theo sẽ được tiếp tục ở phía server thông qua back channel.
(6): Redeeming the Authorization Code
Sau khi nhận được authorization_code
, ứng dụng sẽ kết hợp nó với identity của client (client_id & client_secret) và gửi chúng đến token endpoint của authorization server để đổi lấy access_token. Xem hình dưới đây để biết thêm các tham số được gửi tới token endpoint.
Nhìn vào hình trên bạn sẽ thấy giá trị của grant_type là authorization_code
, đây là giá trị để cho authorization server biết rằng chúng ta muốn đổi authorization code
lấy access_token
.
Nó cũng chứa tham số redirect_uri
, tuy nhiên trong giai đoạn này, authorization server thực sự không cần nó vì client đang nói chuyện với authorization server. Thay vào đó, redirect_uri được sử dụng như một biện pháp bảo mật, nó sẽ so sánh xem giá trị được gửi đến có khớp với redirect_uri mà bạn đã đăng ký với authorization server hay không.
(7): Receiving the Access Token in the Token Endpoint Response
Giả sử rằng yêu cầu được authorization server chấp nhận và xử lý mà không có vấn đề, grant kết thúc và ứng dụng sẽ nhận được access_token
từ authorization server.
(8): Using the Access Token to Call the API
Một khi client lấy được access token, nó sẽ có thể gọi APIs. Tất cả những gì cần làm là đính kèm access token trong phần header và gọi API mà access token có quyền truy cập.
Authorization Code Grant và PKCE
Các tài liệu OAuth2 Security Best Current Practice (BCP) mới nhất đề xuất rằng mọi luồng authorization code nên tận dụng Proof Key for Code Exchange (RFC 7636)
Đây là một extension cho authorization code grant nhằm bảo vệ authorization code khỏi bị đánh cắp khi truyền tải. PKCE ban đầu được thiết kế cho public clients, nơi nó thực hiện các chức năng bảo mật thiết yếu mà chúng ta sẽ mô tả chi tiết trong các bài tiếp theo.
Việc sử dụng nó cho confidential clients không quan trọng bằng, vì có các biện pháp khác đã được thực hiện (kiểm tra state, nonce) giảm thiểu các khía cạnh khác tham gia vào các cuộc tấn công liên quan. Đây cũng là lý do mình sẽ không bao gồm phần triển khai PKCE trong bài này và sẽ dành hẳn một bài riêng để nói về nó.
Conclusion
Như vậy chúng ta đã đi nhanh qua phần xử lý uỷ quyền để gọi APIs. Mình sẽ dành thời gian để nói thêm một số vấn đề liên quan đến:
- Sidebar: Access Tokens vs. ID Tokens
- Sidebar: Essential Authorization Concepts and Terminology
- The Refresh Token Grant
- Other Grant
Sau.
Trong bài tiếp theo chúng ta sẽ cùng nhau xây dựng một ID Provider đóng vai trò là Authorization Server và Resource Server.
Tài liệu tham khảo:
- The OAuth 2.0 Authorization Framework: RFC 6749
- OpenID Connect Core
- OAuth2 and OpenID Connect: The Professional Guide
Again, nếu bạn thấy bài viết này hữu ích, hãy cho mình một upvote và follow để mình có thêm động lực viết những bài sau tốt và chất lượng hơn! Thank you!