Nếu đã bắt tay vào một phần mềm nào đó dù nhỏ thì chắc các bạn cũng đã nghe tới hai khái niệm Authentication và Authorization. Trong bài này ta cùng tìm hiểu và xem cách chúng ta hỗ trợ hai tính năng này trong Mini Web Server thế nào nhé.
Authentication và Authorization là gì?
Authentication là hành động xác định danh tính của một người dùng, nó dùng để trả lời câu hỏi “Người dùng đang truy cập là ai?”. Một trong những phương pháp phổ biến nhất mà chắc ai cũng biết là sử dụng user name/password, nhưng cũng có nhiều phương pháp khác, hỗ trợ cho các usecase khác, như sử dụng các token, client certificate, OTP… Những usecase này có một số quy trình hoặc yêu cầu khác biệt, ví dụ:
- Các API không có giao diện nên không thể yêu cầu người dùng đăng nhập bằng tên đăng nhập hoặc mật khẩu.
- Một số dịch vụ yêu cầu đặc biệt về bảo mật cần người dùng cài đặt trước các certificate trên máy client, do vậy bạn chỉ có thể đăng nhập từ một máy tính cụ thể.
- OTP cho phép bạn đăng nhập dễ dàng mà không cần nhớ mật khẩu…
- Các hard token lưu trữ khóa đăng nhập trong một thiết bị phần cứng giúp bạn đăng nhập một cách bảo mật nhưng vẫn dễ dàng.
- …
Một số hệ thống cho phép bạn chọn một trong những phương pháp xác thực trên, hoặc thậm chí kết hợp chúng lại với nhau, ví dụ bạn phải đăng nhập bằng user/password trên một máy tính có gắn hard token, hay nhập mã OTP sau khi nhập password…
Nhiều phần mềm hiện tại sử dụng một hệ thống thứ ba đảm nhiệm việc xác thực người dùng, từ đó cho phép ta xây dựng mô hình single sign-on (SSO), dùng một tài khoản để truy cập vào nhiều phần mềm khác nhau. Nhờ vậy người phát triển ứng dụng được giải phóng khỏi các tính năng liên quan đến xác thực, vốn cũng rất phức tạp và tốn kém chi phí phát triển lẫn duy trì. Người dùng cũng được giải phóng khỏi việc ghi nhớ nhiều tên đăng nhập/password trên các hệ thống khác nhau, từ đó giúp cho việc bảo mật cũng trở nên dễ dàng hơn. Một ví dụ là hiện có rất nhiều web site cho phép đăng nhập bằng tài khoản Facebook, Google, Apple…
Một phần mềm tốt phải được thiết kế sao cho không phụ thuộc vào phương thức xác thực, nhờ vậy khi có yêu cầu mới về hệ thống, bạn sẽ dễ dàng thay đổi mà không sợ ảnh hưởng đến phần core (phần xử lý các quy trình kinh doanh của hệ thống).
Authorization là hành động xác định quyền hạn mà một người dùng được phép thực hiện. Nôm na là sau khi đã biết “Người dùng đang truy cập là ai?”, ta sẽ trả lời tiếp “Người dùng này được làm những gì”. Tuy Authentication và Authorization là hai hành động riêng biệt, tuy nhiên do mối quan hệ tự nhiên của chúng mà ta luôn nhắc đến chúng như thể là một.
Khác với Authentication, Authorization thông thường được định nghĩa và quản lý bởi hệ thống của bạn, vì các tính năng mỗi hệ thống cung cấp khác nhau, do vậy chỉ có bạn biết chính xác nên quản lý chúng như thế nào. Khi nói đến authorization, ta thường quen với các khái niệm như permission, group, hay role… chúng giúp ta định nghĩa các quyền cho một người dùng cụ thể, hoặc một nhóm người dùng có cùng vai trò.
HTTP hỗ trợ Authentication và Authorization như thế nào?
Khi truy cập vào một tài nguyên được bảo vệ và yêu cầu xác thực, server sẽ trả về lỗi 401 .Unauthorized, đồng thời trả về một header có tên WWW-Authenticate chứa các scheme mà server hỗ trợ, client sẽ dựa vào các scheme này để chuyển đến trang phù hợp cho phép người dùng thực hiện đăng nhập (xác thực).

Với việc xác thức, giao thức HTTP hỗ trợ một header có tên Authorization, cú pháp của header này khá đơn giản:
Authorization: <auth-scheme> <auth-parameters>
Trong đó auth-scheme chỉ ra scheme – cách dữ liệu người dùng đang truy cập được mã hóa (encode) trong auth-parameters. Web server (hoặc web app) đọc header này và giải mã (decode) các tham số trong auth-parameters để định danh người dùng và xác định các quyền mà người dùng có.
Scheme đơn giản nhất là Basic, khi đó auth-parameters sẽ là một chuỗi ghép từ username:password, sau đó được encode dạng Base64. Dễ dàng nhận thấy rằng nếu request bị bắt trên đường truyền, ta có thể đọc lại được username và password một cách dễ dàng, vì vậy thông thường ta chỉ dùng Basic trên các kết nối HTTPS. Thực tế là hiện tại hiếm có website nào còn dùng scheme này vì nó (trông có vẻ) ít bảo mật, và trải nghiệm đăng nhập không tốt.
Một scheme quan trọng được dùng rất phổ biến khi viết các Web API là Bearer, khi đó auth-parameters là một Json Web Token (JWT).
Ta có thể thấy, mỗi khi truy cập vào một tài nguyên được bảo vệ, ta luôn phải gửi kèm Authorization header, và server buộc phải xác thực header này trước khi thực hiện các bước tiếp theo. Nếu sử dụng Basic scheme như đã nói ở trên, server cần làm gì để xác thực? Có lẽ sẽ là kết nối đến database và kiểm tra xem có cặp username/password nào phù hợp không. Đây là một phương thức tốn kém chi phí, sử dụng nhiều tài nguyên server, thậm chí có thể còn tốn kém hơn bản thân chức năng mà API cung cấp.
Các API cũng được thiết kế để phục vụ các chương trình máy tính, chúng đòi hỏi khả năng xử lý lớn hơn nhiều so với các trang web (vốn được thiết kế để phục vụ con người). Bearer cho phép chúng ta xác thực mà không cần kết nối đến cơ sở dữ liệu, bằng cách chứa thông tin cả về user lẫn Security Service Provider (SSP), và cả chữ ký (digital signature) của nó. HTTP Server chỉ cần xác thực chữ ký thành công là có thể lấy được thông tin về user, không có bất kỳ thao tác nào lên cơ sở dữ liệu người dùng (mà thật ra cũng không cần có CSDL này).
Vậy tới đây ta đã biết JWT được dùng làm gì và khi nào, tuy nhiên một vấn đề khác lại xảy ra. Đó là vì server không kết nối đến DB để kiểm tra, vậy nên có thể xảy ra trường hợp tài khoản người dùng không còn hợp lệ sau khi token (JWT) đã được sinh ra, server sẽ không nhận ra và vẫn chấp nhận token. Để giải quyết vấn đề này, người ta đặt tuổi của token ngắn lại, thường thì chỉ tính bằng phút, khi hết hạn người dùng sẽ phải xác thực lại để lấy về token mới với thông tin đã được cập nhật. Nhưng từ đây lại phát sinh ra một vấn đề khác, nếu cứ mỗi vài phút lại phải quay lại trang đăng nhập một lần thì quá phiền phức, nên người ta cấp thêm một token thứ hai với vòng đời dài hơn (thường tính bằng ngày), mỗi khi token thứ nhất hết hạn, ta sẽ gửi token thứ hai lên cho server để xác thực, khi đó server mới kiểm tra và cấp lại một token mới cho client, người dùng chỉ cần xác thực lại nếu quá trình xác thực token thứ hai không thành công.
Token thứ hai này thường được gọi với cái tên là refresh token, và không nhất thiết phải là một JWT.
(Ảnh minh họa từ Mozilla Developer Network)