Sử dụng JWT để xác thực các tệp tải về phía Client.

JWT là gì?

JSON Web Token (JWT) là một phương tiện để đại diện cho các yêu cầu được chuyển giao giữa hai bên. Các xác nhận quyền sở hữu trong JWT được mã hoá dưới dạng một đối tượng JSON được ký kết bằng chữ ký sử dụng JSON Web Signature (JWS) và / hoặc mã hóa bằng JSON Web Encryption (JWE).

Nói một cách đơn giản, nó chỉ là một cách khác để mã hoá đối tượng JSON và sử dụng đối tượng được mã hóa như là một mã thông báo truy cập để xác thực từ máy chủ.

Vấn đề tải xuống là gì?

Trong hầu hết các ứng dụng, chúng ta cần tải các tập tin từ máy chủ nhưng tải tập tin là một công việc khó khăn. URL download thường không phải là các cuộc gọi AJAX mà chúng chủ yếu là bắt chước các sự kiện nhấp chuột trên thẻ neo (anchor tag). Ví dụ:

const downloadFile = (url) => { 
  //url:BASE_URL/v1/api/downloadX?name=test_name;
  const link = document.createElement('a');
  document.body.appendChild(link);
  link.href = url;
  link.setAttribute('type', 'hidden');
  link.click();
}

Nếu ai đó mở URL ở trên (BASE_URL/v1/api/downloadX?name=test_name) trong 1 tab mới, họ sẽ có thể tải tệp trực tiếp mà không cần bất kỳ xác thực nào. Chúng ta không thể thêm XSRF hoặc bất kỳ xác nhận nào khác trên máy chủ (server) vì nó không phải là một cuộc gọi AJAX và bạn không thể đặt headers cho các loại cuộc gọi này.

Giải pháp: JWT để xác thực.

Trước khi bắt đầu bất kỳ hành động download nào trong ứng dụng frontend, đầu tiên chúng ta cần lấy mã thông báo (JWT) từ máy chủ.

Client Code

const downloadFile = async (url) => {

   const { data: { token } } = await axios.get('/v1/api/jwt/get-token');
   // axios: Promise based HTTP client for the browser and node.js
   // You can make simple fetch for the same.
   let finalUrl = url;
   if (url.includes('?')) {
        finalUrl += `&token=${token}`;
   } else {
        finalUrl += `?token=${token}`;
   }

   const link = document.createElement('a');
   document.body.appendChild(link);
   link.href = finalUrl;
   link.setAttribute('type', 'hidden');
   link.click();
}

Server Code

const jwt = require('jsonwebtoken'); // npm install jsonwebtoken
const router = express.Router();

router.get('/v1/api/get-token', (req, res) => {
 const secret = process.env.JWT_SECRET; // Secret from environment. // You can store this is DB as well
 const token = jwt.sign({}, secret, { expiresIn: 2 }); // 2 seconds expiry for JWT token
 return res.json({ token });
});

Cuộc gọi đầu tiên trong mã client ở trên là cuộc gọi AJAX và nó có thể được bảo đảm cho bất kỳ kiểm tra headers dựa trên nào (XSRF vv). Mã này sẽ trả lại một mã thông báo JWT có thời hạn MAX 2 giây. Chúng ta sẽ nối token này cùng với url của hành động nhấp chuột tải xuống hiện tại và gửi mã thông báo đến máy chủ (server) để xác thực. Mã thông báo (token) này sẽ được xác minh bởi middleware trên máy chủ như sau:

const router = express.Router();
const jwt = require('jsonwebtoken'); // npm install jsonwebtoken

const MESSAGES = {
 TOKEN_NOT_FOUND: 'Valid tokens not present ',
 TOKEN_EXPIRES: 'Download token expired'
};

// Middlware for download Verifier
const JWTDownloadVerifier = async (req, res, next) => {
  try {
    const token = req.query.token;
    if (!token) {
      return res.status(400).json({ message: MESSAGES.TOKEN_NOT_FOUND });
    }
    const secret = process.env.JWT_SECRET;
    jwt.verify(token, secret);
  } catch (err) {
    return res.status(403).json({ message: MESSAGES.TOKEN_EXPIRES });
  }
  return next();
};

router.get('/v1/api/downloadX', JWTDownloadVerifier, (req, res, next) {
  const downloadData = getDownloadedData(req.query.name); //ANY DOWNLOAD LOGIC.
  res.writeHead(200, {
      'Content-Type': 'application/octet-stream',
      'Content-Disposition': 'attachment; filename=downloadX.csv'
    });
    res.write(downloadData);
})

Đối với mỗi lần tải xuống mới, chúng ta sẽ tạo ra một mã thông báo (token) tạm thời có thời gian MAX 2 giây (lý tưởng là <200 phần nghìn giây) nhưng chúng ta đặt nó thành 2 giây cho các mạng chậm. Mã thông báo này sẽ không bao giờ được sử dụng nữa và ngăn người dùng mở URL trong tab mới mà không có quyền truy cập của mã thông báo (token).

Trên đây là phần giới thiệu cách sử dụng JWT để xác thực tải tệp tin.