Authentication (xác thực) và Authorization (ủy quyền) — nghe tưởng đơn giản, nhưng bao nhiêu năm đi làm em thấy dev vẫn nhầm như chơi ăn gian. Có những dự án dùng JWT xong tưởng “thế là xong auth”, có những API trả về 401 khi đáng lẽ phải là 403, có những hệ thống lưu password dạng mã hóa 2 chiều thay vì hash. Bài viết này sẽ chỉ ra những hiểu lầm phổ biến nhất về auth mà ngay cả dev 5+ năm kinh nghiệm cũng mắc phải.

1. Nhầm Authentication vs Authorization — “Hai Cái Khác Nhau Mà”

Authentication (AuthN): Xác thực — “Bạn là ai?”
Authorization (AuthZ): Ủy quyền — “Bạn được làm gì?”

Nghe thì dễ, nhưng em đã thấy không ít codebase viết:

// Route trong Laravel - SAI
Route::middleware('auth:api')->get('/admin/users', ...);

// Auth middleware chỉ check bạn đã login chưa
// Nó KHÔNG check bạn có quyền admin không
// Phải dùng thêm policy hoặc gate để check authorization

Hậu quả: User thường login xong có thể truy cập mọi thứ, chỉ vì dev nghĩ “đã auth rồi” mà quên mất bước check quyền.

Giải pháp: Tách biệt rõ hai tầng — middleware AuthN (xác thực token/session) và middleware AuthZ (kiểm tra role/permission). Trong Laravel dùng Gate hoặc Policy, trong Node.js dùng RBAC hoặc CASL.

2. JWT Không Phải Là Giải Pháp Cho Mọi Thứ

JWT (JSON Web Token) đang bị lạm dụng một cách tràn lan. Để hiểu rõ hơn về các loại token, bạn có thể xem bài Opaque Token là gì? So sánh Opaque Token và JWT. Dev thấy JWT “cool”, “stateless”, “không cần database” là đem nhét vào mọi dự án, bất chấp nó có phù hợp hay không.

Sai lầm 1: Lưu sensitive data trong JWT payload

JWT payload chỉ được base64 encode, không phải encrypt. Bất kỳ ai có token đều có thể decode và đọc nội dung. Dev từng lưu cả password, số điện thoại, địa chỉ vào JWT — và nghĩ rằng “nó đã được mã hóa”.

// SAI: Decode JWT bằng tay là thấy hết
// jwt.io - paste token vào là đọc được toàn bộ payload
// Chỉ lưu userId, role, expiry - KHÔNG lưu sensitive data

Sai lầm 2: “JWT stateless nên không cần database”

JWT không thể revoke. Khi bạn issue một token, nó sẽ sống đến khi hết hạn (exp). Nếu user bị hack, bạn không thể làm gì ngoài chờ token hết hạn. Để revoke được, bạn PHẢI có database lưu blacklist hoặc dùng refresh token rotation.

Giải pháp:

  • Dùng short-lived access token (5-15 phút)
  • Dùng refresh token lưu trong database (có thể revoke)
  • Hoặc đơn giản nhất: dùng session-based auth (session ID lưu trong cookie) nếu bạn không có lý do gì để dùng JWT

Thực tế: session-based auth vẫn chiếm đa số trong các web app thương mại. JWT phù hợp cho API service-to-service, mobile app, và single sign-on (SSO), không phải cho web app thông thường.

3. 401 vs 403 — “Dev Cứ Thấy Lỗi Là Trả Về 401”

Đây là lỗi mà em thấy nhiều nhất qua các lần review code:

HTTP StatusÝ nghĩaKhi nào dùng
401 UnauthorizedChưa xác thựcKhông có token, token hết hạn, token sai format
403 ForbiddenĐã xác thực nhưng không có quyềnCó token hợp lệ nhưng không đủ role/permission
200/404 (cho resource không tồn tại)Trả về 404 thay vì 403 để không leak thông tin — xem thêm các trạng thái 4xx HTTP ảnh hưởng đến SEOKhi user không có quyền truy cập resource cụ thể (best practice bảo mật)

Lỗi phổ biến: Middleware auth lỗi trả về 401 — đúng. Nhưng nhiều dev dùng lại cái middleware đó cho cả authorization check, nên ai không có quyền cũng nhận 401 thay vì 403. Hoặc tệ hơn: trả về 401 kèm message “Bạn không có quyền truy cập” — vừa sai status code vừa sai message.

4. Hash Password vs Encrypt Password — “Không Phải Cái Nào Cũng Giống Nhau”

Em từng thấy một dự án dùng AES-256 để “mã hóa” password. Khi hỏi tại sao, dev trả lời: “Để bảo mật, nếu hacker lấy được database cũng không đọc được password”. Vấn đề là: encrypt có thể giải mã, hash thì không.

  • Encrypt (mã hóa 2 chiều): Dùng để bảo vệ dữ liệu khi truyền/nghỉ, có thể giải mã về bản gốc (AES, RSA). Dùng cho: file, message, thông tin thẻ tín dụng.
  • Hash (băm 1 chiều): Không thể giải mã, chỉ có thể so sánh. Dùng cho: password, checksum, integrity check.
  • Hash với Salt: Thêm chuỗi ngẫu nhiên vào mỗi password trước khi hash để chống rainbow table attack.

Hậu quả của việc encrypt password: Nếu key bị lộ, hacker có thể decrypt toàn bộ password — thảm họa bảo mật cấp độ “đóng công ty”.

Đúng: Dùng bcrypt, argon2 hoặc scrypt — các thuật toán hash chuyên dụng cho password. Bcrypt mặc định cost factor 10 (~100ms để hash), chống brute force hiệu quả.

5. Nhầm Lẫn Giữa Token-Based Auth Và Session-Based Auth

Dev thường nghĩ “token auth” và “session auth” là hai khái niệm đối lập. Thực tế, session auth cũng dùng token (session ID). Sự khác biệt nằm ở nơi lưu trữ state:

Tiêu chíSession-BasedJWT-Based
State lưu ở đâuServer (memory/database)Client (trong token)
Revoke được khôngCó — xóa session là xongKhông — trừ khi dùng blacklist
Phù hợp vớiWeb app truyền thống, monolithAPI, mobile, microservices
ScaleCần shared session store (Redis)Không cần — stateless
Bảo mậtCookie httpOnly + secure + sameSite — OWASP Node.js Security Cheat SheetAccess token trong memory, refresh token trong cookie

Không có cái nào “tốt hơn” tuyệt đối. Chọn cái phù hợp với kiến trúc hệ thống của bạn, đừng chạy theo trend.

6. OAuth 2.0 vs OpenID Connect — “SSO Thì Dùng OAuth Là Đủ”

Đây là nhầm lẫn “kinh điển”. Nhiều dev nghĩ OAuth 2.0 là giao thức xác thực (authentication). Sai! OAuth 2.0 là giao thức ủy quyền (authorization), không phải xác thực.

  • OAuth 2.0: Cho phép ứng dụng A truy cập tài nguyên của user trên ứng dụng B (ví dụ: “Cho phép app này đăng lên Facebook của tôi”). Không có thông tin về user.
  • OpenID Connect (OIDC): Là lớp xác thực nằm trên OAuth 2.0 – xem chi tiết tại OpenID Connect specification. Thêm id_token (JWT) chứa thông tin user. Dùng để SSO.

Ví dụ thực tế: “Login với Google” trên website của bạn — đó là OpenID Connect (xác thực), không phải OAuth. Google trả về id_token chứa email, name, avatar. Nếu chỉ dùng OAuth 2.0 thuần, bạn chỉ nhận được access_token mà không biết user là ai.

7. Những Sai Lầm Về Password Policy

Nhiều dev áp đặt password policy quá khắt khe nhưng lại phản tác dụng bảo mật:

  • Bắt buộc đổi password mỗi 30 ngày: NIST SP 800-63 khuyến nghị KHÔNG bắt buộc đổi password định kỳ. Người dùng sẽ đặt password yếu hơn và dễ bị đoán. Chỉ bắt buộc đổi khi có dấu hiệu lộ thông tin.
  • Yêu cầu ký tự đặc biệt + số + chữ hoa/thường: Nghiên cứu cho thấy password dài (>12 ký tự) quan trọng hơn password phức tạp. “iloveyoumydearcat123” khó brute hơn “P@ss1” dù nó không có ký tự đặc biệt.
  • Rate limiting cho login: Đây là thứ bắt buộc phải có, nhưng nhiều dev quên mất. Không rate limit = brute force thành công trong vài phút.

Best practice hiện tại: Khuyến khích dùng passphrase (câu dài dễ nhớ), hỗ trợ đăng nhập bằng magic link hoặc OTP, và bắt buộc multi-factor authentication (MFA) cho tài khoản admin.

8. CORS Không Phải Là Tính Năng Bảo Mật Auth

Em gặp nhiều dev nghĩ: “Set CORS là xong bảo mật, chỉ cho phép domain của mình gọi API”. Sai! CORS là cơ chế của browser, không phải của server. Nó không ngăn được:

  • curl / Postman / HTTP client từ server-side gọi API
  • Extension trình duyệt hoặc tool tự động
  • Request giả mạo từ trình duyệt cũ không hỗ trợ CORS

CORS chỉ là một lớp bảo vệ mỏng cho browser-based attack. Auth thực sự phải dựa trên token, session, CSRF token, và validate ở server-side. Đừng quên caching cũng cần được xử lý đúng cách khi kết hợp với auth — nếu không bạn sẽ gặp lỗi cache trả về dữ liệu của user khác. Đừng ỷ lại vào CORS rồi quên mất những thứ quan trọng hơn.

Kết Luận: Auth Là Kỹ Năng Cơ Bản Mà Không Đơn Giản

Authentication và Authorization là những khái niệm tưởng dễ nhưng dễ sai nhất trong lập trình. Một lỗi auth có thể khiến cả hệ thống sụp đổ — mất dữ liệu, mất tiền, mất uy tín.

Những điều cần nhớ:

  • Phân biệt rõ AuthN (xác thực) và AuthZ (ủy quyền)
  • JWT không phải “viên đạn bạc” — biết khi nào dùng, khi nào không
  • Trả đúng HTTP status code: 401 vs 403
  • Hash password, đừng encrypt — dùng bcrypt/argon2
  • Hiểu rõ OAuth 2.0 (authorization) vs OpenID Connect (authentication)
  • Password policy: dài > phức tạp, không bắt buộc đổi định kỳ
  • CORS không thay thế được server-side auth

Bạn đã bao giờ mắc phải những sai lầm này chưa? Hay có “ca để đời” nào về auth muốn chia sẻ? Comment ở dưới nhé!

Chào các bạn mình là Quốc Hùng , mình sinh ra thuộc cung song tử ,song tử luôn khẳng định chính mình ,luôn luôn phấn đấu vượt lên phía trước ,mình sinh ra và lớn lên tại vùng đất võ cổ truyền ,đam mê của mình là coder ,ngày đi học tối về viết blog ...