Opaque Token là chuỗi ký tự không thể giải mã hay trích xuất thông tin từ phía client, đóng vai trò tham chiếu đến trạng thái xác thực lưu trữ trên server
Nếu như JWT đã trở thành lựa chọn quen thuộc đối với nhiều lập trình viên khi xây dựng các hệ thống xác thực, thì Opaque Token lại nổi bật với những ưu điểm riêng, đặc biệt về mặt bảo mật thông tin. Trong bài viết này, chúng ta sẽ cùng tìm hiểu chi tiết về Opaque Token, so sánh trực tiếp với JWT và khám phá cách triển khai hệ thống xác thực sử dụng Opaque Token với Node.js và TypeScript.
Trước khi đi sâu vào Opaque Token, chúng ta hãy cùng điểm qua một chút về JWT.
JWT (JSON Web Token) là một loại token tự chứa thông tin (self-contained), bao gồm ba phần chính: Header, Payload và Signature, được phân tách bởi dấu chấm (.). Nhờ cấu trúc này, JWT có thể mang theo dữ liệu xác thực, giúp server giảm tải việc tra cứu dữ liệu mỗi lần xác minh token.
xxxxx.yyyyy.zzzzz
userId
, roles
, email
). Dữ liệu này chỉ được mã hóa Base64, không phải mã hóa bảo mật, nên bất kỳ ai cũng có thể giải mã và đọc được.Điểm mấu chốt của JWT là stateless. Server không cần lưu trữ bất kỳ thông tin gì về token sau khi đã phát hành. Khi nhận lại token từ client, server chỉ cần xác thực chữ ký là có thể tin tưởng vào dữ liệu trong payload.
Khác với JWT, Opaque Token (hay còn gọi là Reference Token) chỉ đơn giản là một chuỗi ký tự ngẫu nhiên, không chứa bất kỳ thông tin nào về người dùng hoặc các claims liên quan nếu chỉ nhìn vào nó.
Ví dụ về một Opaque Token: v2.local.adsjSDA723b1-asdnsa...
Token này chỉ đóng vai trò như một “mã tham chiếu”. Khi client gửi token này lên máy chủ, server sẽ phải thực hiện thao tác tra cứu: dùng chuỗi token làm khóa để tìm kiếm thông tin session tương ứng trong cơ sở dữ liệu (có thể là Redis, PostgreSQL hoặc một hệ thống lưu trữ khác).
Quy trình hoạt động tổng quát của Opaque Token như sau:
Authorization: Bearer <token>
cho những lần request xác thực tiếp theo.Việc lựa chọn giữa Opaque Token và JWT phụ thuộc rất nhiều vào yêu cầu của dự án.
Tiêu chí | Opaque Token | JWT (JSON Web Token) |
---|---|---|
Cấu trúc | Chuỗi ký tự ngẫu nhiên, không có ý nghĩa. | Cấu trúc header.payload.signature chứa dữ liệu (claims). |
Trạng thái | Stateful. Server phải lưu trữ thông tin về token. | Stateless. Server không cần lưu trữ gì, mọi thông tin đã có trong token. |
Bảo mật | Cao hơn. Vì token không chứa dữ liệu, nếu bị lộ cũng vô dụng. Dễ dàng thu hồi (revoke) ngay lập tức bằng cách xóa khỏi DB. | Thấp hơn. Payload có thể bị giải mã và đọc. Rất khó để thu hồi token trước khi nó hết hạn. |
Hiệu năng | Chậm hơn. Mỗi lần xác thực token đòi hỏi một lượt truy vấn database, gây ra độ trễ. | Nhanh hơn. Xác thực chỉ cần tính toán chữ ký bằng CPU, không cần I/O. |
Kích thước | Nhỏ gọn. Thường là một chuỗi có độ dài cố định. | Lớn hơn. Kích thước tăng theo số lượng claims trong payload. |
Khả năng mở rộng | Phức tạp hơn trong microservices. Các service cần phải có cách để gọi đến một nguồn xác thực trung tâm (centralized auth service/database). | Dễ mở rộng hơn. Bất kỳ service nào có secret key đều có thể tự xác thực token mà không cần phụ thuộc vào bên ngoài. |
Khi nào nên sử dụng Opaque Token?
Khi nào nên sử dụng JWT?
Trong ví dụ này, chúng ta sẽ triển khai cơ chế xác thực sử dụng Opaque Token, với Redis làm nơi lưu trữ session nhờ tốc độ truy xuất vượt trội.
Yêu cầu:
Cấu trúc thư mục:
Txt
opaque-token-example/
├── src/
│ ├── authMiddleware.ts
│ └── server.ts
├── package.json
└── tsconfig.json
Bash
mkdir opaque-token-example
cd opaque-token-example
npm init -y
npm install express redis uuid
npm install -D typescript @types/express @types/node @types/uuid ts-node-dev
Tạo file tsconfig.json
:
JSON
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
src/server.ts
: Đây là file chính, định nghĩa các endpoints cho việc đăng nhập, đăng xuất và truy cập tài nguyên được bảo vệ.
Javascript
import express, { Request, Response, NextFunction } from 'express';
import { createClient } from 'redis';
import { v4 as uuidv4 } from 'uuid';
import { authMiddleware } from './authMiddleware';
const app = express();
app.use(express.json());
// Khởi tạo Redis client
export const redisClient = createClient({
// url: 'redis://your-redis-instance' // Cấu hình nếu cần
});
redisClient.on('error', (err) => console.log('Redis Client Error', err));
// Dữ liệu người dùng giả lập
const users = [{
id: 'user-1',
username: 'testdev',
password: 'password123'
}];
// Endpoint: Đăng nhập
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username && u.password === password);
if (!user) {
return res.status(401).json({ message: 'Invalid credentials' });
}
// 1. Tạo Opaque Token ngẫu nhiên
const token = uuidv4();
// 2. Lưu thông tin session vào Redis với token làm key
// Session sẽ hết hạn sau 1 giờ (3600 giây)
await redisClient.set(token, JSON.stringify({ userId: user.id }), {
EX: 3600
});
// 3. Trả token về cho client
return res.json({ accessToken: token });
});
// Endpoint: Lấy thông tin cá nhân (được bảo vệ)
// Chúng ta sẽ sử dụng middleware xác thực ở đây
app.get('/profile', authMiddleware, (req: Request, res: Response) => {
// Nhờ có middleware, chúng ta có thể truy cập thông tin user qua req.user
res.json({ message: 'Đây là thông tin profile của bạn', user: req.user });
});
// Endpoint: Đăng xuất
app.post('/logout', authMiddleware, async (req, res) => {
// Để đăng xuất, chỉ cần xóa token khỏi Redis
const token = req.headers.authorization?.split(' ')[1];
if (token) {
await redisClient.del(token);
}
res.status(200).json({ message: 'Logged out successfully' });
});
const startServer = async () => {
await redisClient.connect();
app.listen(3000, () => {
console.log('Server is running on http://localhost:3000');
});
};
startServer();
src/authMiddleware.ts
: Middleware này sẽ chặn các request, kiểm tra token và xác thực nó với Redis.
Javascript
import { Request, Response, NextFunction } from 'express';
import { redisClient } from './server';
// Mở rộng kiểu Request của Express để chứa thông tin user
declare global {
namespace Express {
interface Request {
user?: any;
}
}
}
export const authMiddleware = async (req: Request, res: Response, next: NextFunction) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ message: 'Authorization token required' });
}
const token = authHeader.split(' ')[1];
try {
// Tra cứu token trong Redis
const sessionData = await redisClient.get(token);
if (!sessionData) {
// Nếu không tìm thấy -> token không hợp lệ hoặc đã hết hạn
return res.status(401).json({ message: 'Invalid or expired token' });
}
// Gắn thông tin user vào request để các handler sau có thể sử dụng
req.user = JSON.parse(sessionData);
next();
} catch (error) {
console.error('Authentication error:', error);
return res.status(500).json({ message: 'Internal server error' });
}
};
Thêm script vào package.json
:
JSON
"scripts": {
"start": "ts-node-dev --respawn --transpile-only src/server.ts"
}
Bây giờ, chạy lệnh:
Bash
npm start
Không có giải pháp xác thực nào phù hợp cho tất cả mọi trường hợp. Cả Opaque Token và JWT đều là những công cụ hiệu quả, mỗi loại có những ưu thế và hạn chế riêng.
Là một lập trình viên, việc nắm vững nguyên lý hoạt động của cả hai phương pháp này sẽ giúp bạn đưa ra quyết định đúng đắn, đáp ứng các yêu cầu kiến trúc và bảo mật của từng dự án. Hy vọng bài viết đã giúp bạn hiểu rõ hơn về Opaque Token cũng như cách áp dụng thực tế trong Node.js và TypeScript.
You need to login in order to like this post: click here
YOU MIGHT ALSO LIKE