Đang tải...

OAuth2 Account Takeovers: Xây dựng Kiến trúc Social Login Bất Khả Xâm Phạm

07/05/2026
7 phút đọc
OAuth2 Account Takeovers: Xây dựng Kiến trúc Social Login Bất Khả Xâm Phạm
Chào anh em, hôm nay tôi sẽ chia sẻ với anh em chủ đề security. **Bài viết này dành cho ai?** Dành cho các Software Engineer, Backend Developer và Architect đang tìm cách chu?...

Chào anh em, hôm nay tôi sẽ chia sẻ với anh em chủ đề security.

Bài viết này dành cho ai? Dành cho các Software Engineer, Backend Developer và Architect đang tìm cách chuẩn hóa hệ thống xác thực của mình, tránh việc phụ thuộc quá nhiều vào các thư viện "hộp đen" (black-box) và đảm bảo an toàn tuyệt đối trước các đợt tấn công Account Takeover.


Sự An Toàn Ảo Tưởng Của Social Login

Chúng ta thường nghĩ rằng: "Chỉ cần dùng Google/GitHub Login là xong phần bảo mật, Google lo hết rồi". Thực tế, lỗ hổng hiếm khi nằm ở Google, mà nằm ở cách chúng ta tích hợp (integration) hệ thống của mình với Provider.

Trong kiến trúc bảo mật gần đây, chúng tôi quyết định đập bỏ hoàn toàn các thư viện trung gian như Passport.js để tự xây dựng một luồng (flow) OAuth2 tùy chỉnh, dựa trên nguyên tắc Zero-Trust.

Hãy cùng phân tích tại sao.

1. Cạm bẫy Dependency: Tại sao tôi từ bỏ Passport.js?

Passport.js là một thư viện tuyệt vời để bắt đầu, nhưng trong kiến trúc Enterprise, nó đóng gói toàn bộ luồng OAuth thành một "hộp đen". Bạn không kiểm soát được chính xác request nào được gọi, xử lý lỗi ra sao ở tầng Domain, và quan trọng nhất là tăng diện mạo tấn công (Attack Surface) vì phụ thuộc vào một chuỗi các package nhỏ lẻ.

Thay vào đó, chúng tôi sử dụng Axios để tự thực hiện luồng trao đổi Token (Token Exchange):

// https://github.com/paudang/nodejs-social-auth/blob/main/src/infrastructure/auth/socialAuthService.ts
export class GoogleProvider implements ISocialProvider {
 name = 'Google';
 async getProfile(code: string, redirectUri: string): Promise<ISocialProfile> {
 try {
 const params = new URLSearchParams();
 params.append('code', code);
 params.append('client_id', process.env.GOOGLE_CLIENT_ID!);
 params.append('client_secret', process.env.GOOGLE_CLIENT_SECRET!);
 params.append('redirect_uri', redirectUri);
 params.append('grant_type', 'authorization_code');

 // Tự chủ động exchange token thay vì dùng black-box lib
 const tokenResponse = await axios.post('https://oauth2.googleapis.com/token', params.toString(), {
 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
 });

 const { access_token } = tokenResponse.data;
 const profileResponse = await axios.get('https://www.googleapis.com/oauth2/v2/userinfo', {
 headers: { Authorization: `Bearer ${access_token}` },
 });

 return {
 id: profileResponse.data.id,
 email: profileResponse.data.email,
 name: profileResponse.data.name,
 };
 } catch (error) {
 // Bắt lỗi và kiểm soát hoàn toàn ở tầng Infrastructure
 throw new Error('Failed to authenticate with Google');
 }
 }
}

2. Vấn đề Blind Linking & Account Takeover (ATO)

Một trong những lỗ hổng chết người của Social Login là Blind Linking (Liên kết tài khoản mù quáng). Nếu một kẻ tấn công tạo một tài khoản GitHub với email của bạn (dù chưa verify), và hệ thống tự động gộp (merge) tài khoản GitHub đó vào user có sẵn dựa trên email -> Kẻ tấn công vừa chiếm được tài khoản của bạn.

Để giải quyết vấn đề này, luồng kiểm tra của chúng ta tách bạch googleIdgithubId, đồng thời vô hiệu hóa (disabled) mật khẩu nếu user được sinh ra từ mạng xã hội:

// https://github.com/paudang/nodejs-social-auth/blob/main/src/usecases/auth/socialLoginUseCase.ts
// 1. Find or create user
let user = await this.userRepository.findByEmail(profile.email);

if (!user) {
 // Tạo user mới, trường Password là null để vô hiệu hóa đăng nhập truyền thống
 user = new User(
 null,
 profile.name,
 profile.email,
 null, // Password = null
 this.provider.name === 'Google' ? profile.id : null,
 this.provider.name === 'GitHub' ? profile.id : null,
 );
 user = await this.userRepository.save(user);
} else {
 // Link social ID một cách có kiểm soát
 let updated = false;
 if (this.provider.name === 'Google' && !user.googleId) {
 user.googleId = profile.id;
 updated = true;
 }
 // ... Update user 
}

3. Đồng bộ hóa với "Nuclear Revoke"

Trong bài viết trước về "The Illusion of Stateless Security", tôi đã đề cập đến Nuclear Revoke - cơ chế vô hiệu hóa phiên bản làm việc diện rộng bằng Redis.

Khi user đăng nhập bằng Google, chúng ta không dùng session của Google để duy trì đăng nhập. Chúng ta lập tức chuyển đổi (exchange) nó thành JWT nội bộ của chúng ta, được bảo vệ bằng Refresh Token Rotation và JTI Tracking:

// https://github.com/paudang/nodejs-social-auth/blob/main/src/interfaces/controllers/auth/authController.ts
// Sau khi xác thực Social thành công
const { user, accessToken, refreshToken } = await useCase.execute(code as string, redirectUri);
const refreshJti = JwtService.decodeToken(refreshToken)?.jti;

// Store refresh token vào Redis List (Nuclear Revoke System)
const cacheKey = `refresh_tokens:${userId}`;
const activeTokens = await cacheService.get<string[]>(cacheKey) || [];
activeTokens.push(refreshJti!);
await cacheService.set(cacheKey, activeTokens, 7 * 24 * 60 * 60);

// Trả về qua Cookie thay vì body để tránh XSS
res.cookie('accessToken', accessToken, { httpOnly: true, secure: true, sameSite: 'lax' });
res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: true, sameSite: 'lax' });
res.redirect('/');

4. Luồng tương tác hoàn chỉnh (Sequence Diagram)

Dưới đây là sơ đồ kiến trúc thể hiện toàn bộ quy trình từ lúc User click vào nút Login, quá trình Verify State (chống CSRF), đến khi phát hành Internal JWT:

Note: Quá trình Verify 'state' (chống CSRF) trong sơ đồ trên là kiến trúc lý tưởng. Tính năng sinh mã ngẫu nhiên bằng Cryptography cho state hiện đang được phát triển và sẽ được tự động hóa trong phiên bản cập nhật tiếp theo của tool.

Tổng kết

Nếu bạn muốn trải nghiệm luồng bảo mật hoàn chỉnh này (MVC hoặc Clean Architecture) mà không phải tự tay viết lại, bạn có thể chạy dòng lệnh sau từ dự án open-source của tôi:

npx nodejs-quickstart-structure@latest init -n "my-secure-app" -l "TypeScript" -a "Clean Architecture" -d "PostgreSQL" --db-name "demo" -c "REST APIs" --caching "Redis" --ci-provider "GitHub Actions" --auth JWT --social-auth Google GitHub --no-include-security --advanced-options

Tài nguyên cho Architect

📚 Nguồn: Viblo

Bình luận

0 bình luận

Email không hiển thị công khai.

Chưa có bình luận nào. Hãy là người đầu tiên bình luận.

Chia sẻ bài viết

Cần tư vấn?

Liên hệ với chúng tôi để được hỗ trợ

Liên hệ ngay

Bài viết liên quan

1.0 Site To Buy Verified Bybit Account
21/06/2026

1.0 Site To Buy Verified Bybit Account

1.0 Site To Buy Verified Bybit Account 💬 Need Assistance? All Time Service, Just Talk To- ✨ Telegram: @usamarketit ✨ Email: usamarketit@gmail.com ✨ WhatsApp: +1(772)563-8300 ✨ Discord: usam...

Đọc thêm
VA88 MN
20/06/2026

VA88 MN

VA88 - Nhà cái cá cược thể thao, game đổi thưởng top #1 Châu Á 2026. Đa dạng các sản phẩm cá cược Bóng đá, Live Casino, Lô đề online, Bắn cá, Nổ hũ Địa ch?...

Đọc thêm
Best Sites to Buy Aged Reddit Accounts: Top 5 Trusted Providers
20/06/2026

Best Sites to Buy Aged Reddit Accounts: Top 5 Trusted Providers

# Old Reddit Accounts: Value, Benefits, Security, and Community Reputation ![](https://images.viblo.asia/bff98096-0daa-416e-9760-d90647fab604.jpg) Introduction Reddit is one of the world's largest onl...

Đọc thêm

Bắt đầu dự án của bạn

Hãy để Flash Dev đồng hành cùng bạn

Liên hệ ngay