Hiểu về Access Token và Refresh Token trong ứng dụng MERN
Chào các lập trình viên, hôm nay chúng ta sẽ cùng nhau khám phá một chủ đề quan trọng trong phát triển ứng dụng web, đó là Access Token và Refresh Token trong ứng dụng MERN (MongoDB, Express, React, Node.js). Trong bài viết này, chúng ta sẽ tìm hiểu cách thức hoạt động của chúng, lý do tại sao chúng ta cần sử dụng và cách thực hiện chúng một cách hiệu quả.
Mục Lục
- JWT là gì?
- Access Token và Refresh Token là gì?
- Tại sao chúng ta cần Access Token và Refresh Token?
- Nếu không sử dụng Access Token và Refresh Token thì sao?
- Tại sao cần hai loại token?
- Cách thức hoạt động của Access Token và Refresh Token
- Cách tạo Access Token và Refresh Token
- Cách tái tạo Access Token dựa trên Refresh Token
- Các thực tiễn tốt nhất khi sử dụng Token
- Các cạm bẫy thường gặp
- Mẹo hiệu suất
- Khắc phục sự cố
JWT là gì?
JSON Web Token (JWT) là một tiêu chuẩn được tạo ra bởi IETF, được sử dụng để tạo các token nhằm mục đích bảo mật trong ứng dụng của bạn. JWT thường được dùng trong xác thực, kiểm soát quyền truy cập, thanh toán, và quản lý admin.
JWT được tạo ra từ một payload của người dùng, ví dụ: { email, username, id }
. Cùng với payload này, bạn cũng cần sử dụng một khóa bí mật, mà bạn nên lưu trữ trong tệp .env
của mình.
Nếu bạn không biết khóa bí mật của mình nên là gì, hãy chạy lệnh sau:
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
Sao chép và dán lệnh trên và lưu chuỗi vào tệp bí mật của bạn.
Quay lại với JWT, với payload và chữ ký, một JWT cũng có một header, chứa loại (JWT) và thuật toán mã hóa, chẳng hạn như HMAC SHA256 hoặc RSA.
Token của bạn trông như thế này:
xxxx.yyyy.zzzz = Header.Payload.Signature
Trong giai đoạn này, bạn không cần tập trung quá nhiều vào chữ ký; chỉ cần biết rằng nó có mặt để xác minh rằng thông điệp không bị xâm hại.
Access Token và Refresh Token là gì?
Access Token → Đây là token được sử dụng để truy cập vào các tài nguyên đã được xác thực, thực hiện các yêu cầu nhất định, thêm hoặc chỉnh sửa tài nguyên, v.v. Tuy nhiên, access token thường hết hạn nhanh (10–15 phút) vì nếu nó bị xâm phạm, nó có thể bị lạm dụng.
Refresh Token → Đây giống như access token nhưng có thời gian tồn tại lâu hơn (ví dụ 7 ngày). Nó cho phép người dùng giữ đăng nhập mà không cần phải nhập lại thông tin xác thực mọi 15 phút. Khi access token hết hạn, refresh token sẽ được sử dụng để tạo ra một access token mới.
Tại sao chúng ta cần Access Token và Refresh Token?
Bạn có thể đã gặp phải những vấn đề như:
- Một người dùng cần đăng nhập trước khi thêm một dự án.
- Chỉ có admin mới có thể xem thông tin chi tiết của người dùng khác.
Trong những trường hợp như vậy, bạn cần một cách để xác minh rằng yêu cầu đến từ người dùng đúng.
Đó là lúc JWT phát huy tác dụng. Bạn có thể tạo một token bao gồm vai trò hoặc quyền hạn của người dùng, và sau đó xác minh nó trong mỗi yêu cầu trước khi cho phép truy cập.
Nếu không sử dụng Access Token và Refresh Token thì sao?
Nếu chúng ta không sử dụng token, chúng ta sẽ phải:
- Gửi tên người dùng và mật khẩu trong mỗi yêu cầu (rất không an toàn).
- Duy trì dữ liệu phiên làm việc trong backend cho mỗi người dùng đã đăng nhập (không thể mở rộng cho các ứng dụng lớn).
- Phơi bày bản thân trước những rủi ro cao hơn về việc đánh cắp phiên làm việc. Tóm lại, nếu không có access và refresh token, việc xác thực trở nên lộn xộn, không an toàn và khó quản lý.
Tại sao cần hai loại token?
Có thể bạn đang nghĩ: Tại sao không chỉ sử dụng một token mà không bao giờ hết hạn hoặc hết hạn sau vài ngày?
Vấn đề với một token dài hạn duy nhất là an ninh.
- Nếu token đó bị đánh cắp, kẻ tấn công có thể sử dụng nó trong suốt thời gian nó có hiệu lực. Nếu nó có hiệu lực trong 7 ngày, thì đó là 7 ngày truy cập trái phép.
- Ngược lại, nếu bạn làm cho token có thời gian sống ngắn (như 10 phút), người dùng sẽ phải đăng nhập lại mỗi 10 phút, điều này rất tồi tệ cho trải nghiệm người dùng.
Đây là token mang tính chất bearer, tức là nếu một hacker có token, hắn trở thành chủ sở hữu và có thể sử dụng nó theo bất kỳ cách nào hắn thích, giống như tiền mặt. Ai sở hữu tờ giấy đó có thể chi tiêu.
Giải pháp là sử dụng hai token:
- Access Token (ngắn hạn), được sử dụng cho các yêu cầu thực tế. Ngay cả khi nó bị xâm phạm, kẻ tấn công chỉ có thể sử dụng nó trong một vài phút.
- Refresh Token (dài hạn), được lưu trữ an toàn trong cookie chỉ HTTP, và được sử dụng để âm thầm yêu cầu một access token mới mà không làm phiền người dùng.
Bằng cách này, bạn nhận được lợi ích của cả hai thế giới:
Bảo mật tốt (vì access token hết hạn nhanh).
Trải nghiệm người dùng tốt (vì refresh token cho phép bạn giữ đăng nhập).
Cách thức hoạt động của Access Token và Refresh Token
Dưới đây là quy trình tổng quát:
- Người dùng đăng nhập với thông tin xác thực của họ.
- Backend xác minh thông tin xác thực và tạo access token và refresh token.
- Access token được gửi trong phản hồi (thường trong body hoặc headers).
- Refresh token được gửi dưới dạng cookie httpOnly để đảm bảo an toàn.
- Đối với mỗi yêu cầu API, access token được đính kèm trong header (Authorization: Bearer ).
- Khi access token hết hạn, frontend tự động sử dụng refresh token để yêu cầu một cái mới.
Cách tạo Access Token và Refresh Token
Dưới đây là một sơ đồ quy trình đơn giản cho thấy cách trình duyệt nhận dữ liệu trở lại để xác minh.
Ví dụ mã:
typescript
import jwt from "jsonwebtoken";
interface UserPayload {
id: string;
username: string;
}
function generateAccessToken(payload: UserPayload): string {
return jwt.sign(payload, process.env.JWT_SECRET as string, { expiresIn: "10m" });
}
function generateRefreshToken(payload: UserPayload): string {
return jwt.sign(payload, process.env.JWT_SECRET as string, { expiresIn: "7d" });
}
import { Request, Response } from "express";
export const SignUp = async (req: Request, res: Response) => {
try {
// Logic kinh doanh của bạn (tạo người dùng, lưu vào CSDL, v.v.)
const newUser = { _id: "12345", username: "testUser" }; // Chỉ là ví dụ
const accessToken = generateAccessToken({
id: newUser._id.toString(),
username: newUser.username,
});
const refreshToken = generateRefreshToken({
id: newUser._id.toString(),
username: newUser.username,
});
res.cookie("jwt", refreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
});
return res.status(201).json({
token: accessToken,
});
} catch (error) {
console.log(error);
return res.status(500).json({ message: "Có gì đó không ổn" });
}
};
Cách tái tạo Access Token dựa trên Refresh Token
Mỗi khi frontend (như Axios) nhận được lỗi 401 Unauthorized, nó sẽ thực hiện một cuộc gọi API đến backend với refresh token. Backend xác minh refresh token, và nếu nó hợp lệ, tạo ra một access token mới.
Ví dụ:
typescript
import jwt from "jsonwebtoken";
import { Request, Response } from "express";
export const RefreshToken = async (req: Request, res: Response) => {
const token = req.cookies.jwt;
if (!token) {
return res.status(401).json({ message: "Không tìm thấy refresh token" });
}
try {
const payload = jwt.verify(token, process.env.JWT_SECRET as string) as {
id: string;
username: string;
};
const newAccessToken = jwt.sign(payload, process.env.JWT_SECRET as string, {
expiresIn: "10m",
});
return res.status(201).json({
token: newAccessToken,
});
} catch (err) {
return res.status(403).json({
message: "Refresh Token đã hết hạn hoặc không hợp lệ",
});
}
};
Các thực tiễn tốt nhất khi sử dụng Token
- Bảo mật thông tin: Luôn luôn lưu trữ khóa bí mật trong môi trường an toàn.
- Chỉ định thời gian sống cho token: Hãy chắc chắn rằng access token có thời gian sống ngắn và refresh token có thời gian sống dài hơn.
- Sử dụng cookie HTTP-only: Để lưu trữ refresh token nhằm bảo vệ nó khỏi các tấn công XSS.
- Thực hiện xác thực hai yếu tố: Để tăng cường bảo mật cho tài khoản người dùng.
Các cạm bẫy thường gặp
- Lưu trữ token không an toàn: Tránh lưu trữ token trong localStorage hoặc sessionStorage.
- Không kiểm tra token hợp lệ: Đảm bảo rằng bạn luôn xác minh token trước khi cho phép truy cập vào tài nguyên.
- Thiếu các biện pháp bảo mật bổ sung: Đừng chỉ dựa vào token; hãy kết hợp với các biện pháp bảo mật khác.
Mẹo hiệu suất
- Giảm thiểu số lượng yêu cầu API: Sử dụng caching để giảm tải cho backend.
- Tối ưu hóa xác thực: Giảm thời gian xử lý khi xác thực token.
- Theo dõi và phân tích hiệu suất: Sử dụng các công cụ giám sát để theo dõi hiệu suất của ứng dụng.
Khắc phục sự cố
- Token không hợp lệ: Kiểm tra xem token có hết hạn không hoặc có bị xâm phạm không.
- Lỗi không tìm thấy refresh token: Đảm bảo rằng cookie được gửi đúng cách từ frontend.
Kết luận
Hi vọng bài viết này đã giúp bạn hiểu rõ hơn về Access Token và Refresh Token trong ứng dụng MERN. Việc nắm vững cách thức hoạt động của chúng là rất quan trọng trong việc phát triển ứng dụng web an toàn và hiệu quả. Nếu bạn thấy bài viết hữu ích, hãy chia sẻ với những người khác đang tìm hiểu về chủ đề này. Chúc bạn một ngày làm việc hiệu quả và vui vẻ!