Opaque Token là gì? So sánh với JWT

Opaque Token là một chuỗi không thể giải mã hay lấy thông tin gì từ phía client – nó chỉ đóng vai trò như một "mã tham chiếu" tới phiên đăng nhập được lưu ở phía server.

 

Tin tức công nghệ, opaque token, jwt

 

1. JWT (JSON Web Token) là gì?

 

Trước khi phân tích Opaque Token, hãy nhắc lại khái niệm cơ bản về JWT.

 

JWT là loại token có thể tự mang theo thông tin người dùng. Nó có 3 phần: Header, Payload và Signature – được nối bằng dấu .


Ví dụ: xxxxx.yyyyy.zzzzz

 

  • Header: Chứa thông tin về thuật toán dùng để ký token.

 

  • Payload: Lưu các claims như userId, roles, email… Tuy nhiên, phần này chỉ được mã hóa base64 nên dễ bị đọc bởi bất kỳ ai.

 

  • Signature: Giúp đảm bảo token không bị chỉnh sửa khi truyền tải.

 

Đặc điểm nổi bật của JWT là stateless: Server không cần lưu trạng thái token – mọi thông tin đã nằm trong payload. Khi client gửi lại token, server chỉ cần xác thực chữ ký là đủ.

 

2. Opaque Token là gì?

 

Khác với JWT, Opaque Token (hay còn gọi là Reference Token) chỉ là một chuỗi ngẫu nhiên không mang thông tin gì bên trong.

 

Ví dụ: v2.local.adsjSDA723b1-asdnsa...

 

Token này không tự nói lên điều gì – nó chỉ được dùng như khóa để truy xuất thông tin phiên đăng nhập (session) trên server.

 

Cách hoạt động của Opaque Token:

 

  • Người dùng gửi thông tin đăng nhập cho Auth Server.

 

  • Sau khi xác thực thành công, server tạo một session và lưu thông tin người dùng vào database (như Redis hoặc PostgreSQL).

 

  • Server sinh ra một chuỗi token ngẫu nhiên, liên kết với session vừa tạo.

 

  • Token được trả về client.

 

  • Mỗi lần request, client gửi token trong header Authorization: Bearer <token>.

 

  • API Server sẽ kiểm tra token bằng cách truy vấn thông tin từ Auth Server hoặc từ database.

 

  • Nếu token hợp lệ, server sẽ xử lý yêu cầu với thông tin người dùng đã được lưu.

 

3. So sánh Opaque Token và JWT

 

Việc lựa chọn giữa hai loại token tùy thuộc vào yêu cầu hệ thống. Bảng so sánh dưới đây giúp bạn hiểu rõ sự khác biệt:

 

Tiêu chí

Opaque Token

JWT (JSON Web Token)

Cấu trúc Chuỗi ngẫu nhiên, không chứa thông tin gì. Có 3 phần: header, payload, signature – có thể giải mã payload.

Trạng thái

Có trạng thái (stateful): server cần lưu session. Không trạng thái (stateless): không cần lưu gì trên server.

Bảo mật

Cao hơn: nếu token bị lộ cũng không có thông tin. Dễ thu hồi. Thấp hơn: payload có thể bị đọc. Thu hồi khó nếu chưa hết hạn.

Hiệu năng

Chậm hơn: cần truy vấn DB để xác thực. Nhanh hơn: chỉ cần xác thực chữ ký, không cần I/O.

Kích thước

Gọn nhẹ, độ dài token cố định. Thường dài hơn do chứa thông tin bên trong.

Mở rộng trong hệ thống

Phức tạp hơn với kiến trúc microservices – cần gọi tới service xác thực trung tâm. Dễ mở rộng hơn – các service có thể tự xác thực token bằng secret.

 

Khi nào nên chọn Opaque Token?

 

  • Tính bảo mật cao là ưu tiên hàng đầu: Ví dụ khi người dùng đổi mật khẩu, bạn cần thu hồi toàn bộ token ngay lập tức.

 

  • Dành cho ứng dụng nội bộ: Các app thuộc cùng hệ sinh thái, xác thực qua API nội bộ là hợp lý.

 

  • Không muốn để lộ dữ liệu người dùng: Token hoàn toàn không chứa bất kỳ thông tin gì có thể bị lộ.

 

Khi nào nên chọn JWT?

 

  • Hiệu năng là quan trọng: Không cần truy vấn DB nên phù hợp với hệ thống có tần suất request lớn.

 

  • Microservices: Giúp các service tự xử lý xác thực mà không cần phụ thuộc vào trung tâm.

 

  • Tích hợp bên thứ ba: Dễ dùng cho các ứng dụng ngoài muốn xác thực người dùng của bạn.

 

4. Hướng dẫn triển khai Opaque Token với Node.js, Express và TypeScript

 

Trong phần này, ta sẽ xây dựng hệ thống xác thực sử dụng Opaque Token. Redis sẽ được dùng làm nơi lưu session vì có tốc độ xử lý rất nhanh.

 

Yêu cầu:

 

  • Máy tính đã cài đặt Node.js

 

  • Có Redis đang chạy trong máy hoặc trên server

 

4.1 Khởi tạo dự án và cài đặt thư viện

 

Cấu trúc thư mục:

 

opaque-token-example/
├── src/
│   ├── authMiddleware.ts
│   └── server.ts
├── package.json
└── tsconfig.json
 
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:

{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}
 

4.2 Viết mã nguồn

 

src/server.ts: File chính, định nghĩa các endpoint như login, logout, và truy cập tài nguyên.

 

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 xác thực các request dựa vào token gửi lên.

 

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' });
 
    }
 
};
 

4.3 Chạy ứng dụng

 

Thêm script vào package.json:

 

"scripts": {
 
  "start": "ts-node-dev --respawn --transpile-only src/server.ts"
}
 

Chạy ứng dụng bằng lệnh:

 

npm start
 

5. Kết luận

 

Không có loại token nào tốt hơn tuyệt đối – chỉ có giải pháp phù hợp với từng bài toán.

 

  • JWT thích hợp cho hệ thống yêu cầu hiệu năng cao và ít phụ thuộc vào server.

 

  • Opaque Token phù hợp với các ứng dụng cần kiểm soát chặt chẽ session và tăng cường bảo mật.

 

Việc hiểu rõ cách hoạt động của cả hai sẽ giúp bạn xây dựng hệ thống xác thực đúng đắn và linh hoạt hơn. Mong rằng bài viết này đã giúp bạn nắm vững khái niệm và cách ứng dụng Opaque Token trong thực tế với Node.js + TypeScript.

 

 HỖ TRỢ TRỰC TUYẾN