VISEN GROUP
Technology & Engineering

Tự làm tính năng Đăng nhập bằng Google (OAuth 2.0): Luồng chuẩn và 5 lỗi bảo mật thường gặp

September 24, 20264 min readUncategorized

Nút bấm “Tiếp tục với Google” đã trở thành tiêu chuẩn trên hầu hết website hiện đại. Khách truy cập không cần nhớ mật khẩu mới, bạn không phải lo chuyện mã hóa hay khôi phục mật khẩu, và tỷ lệ chuyển đổi đăng ký tài khoản tăng lên rõ rệt.

Dù vậy, nhiều lập trình viên mới bắt đầu vẫn nhầm lẫn giữa việc “xác thực” (Authentication) với “phân quyền” (Authorization), hoặc lưu trực tiếp token ở phía trình duyệt một cách thiếu an toàn. Bài viết này sẽ hướng dẫn bạn dựng luồng Google OAuth 2.0 chuẩn cho ứng dụng web, kèm cách xử lý session đúng quy cách.

1. Google OAuth 2.0 hoạt động như thế nào?

Thay vì lưu mật khẩu của người dùng vào database, bạn ủy quyền bước kiểm tra danh tính cho Google thông qua 4 bước cơ bản:

  • Khách bấm đăng nhập: Website chuyển hướng người dùng đến trang cấp quyền chính thức của Google (accounts.google.com).
  • Google xác nhận: Người dùng chọn tài khoản Gmail và đồng ý chia sẻ thông tin cơ bản (email, họ tên, avatar).
  • Trả mã xác thực (Authorization Code): Google chuyển hướng ngược lại website của bạn kèm một đoạn mã ngắn dùng một lần (code).
  • Đổi mã lấy thông tin: Server của bạn bí mật gửi mã này lên Google API để đổi lấy id_token hoặc thông tin tài khoản đã được ký số an toàn.

2. Tạo dự án và lấy Client ID trên Google Cloud Console

  1. Truy cập vào Google Cloud Console (console.cloud.google.com) và tạo một project mới.
  2. Vào mục APIs & Services > Credentials, chọn Create Credentials > OAuth client ID.
  3. Cấu hình loại ứng dụng là Web application.
  4. Khai báo Authorized redirect URIs (URI chuyển hướng hợp lệ), ví dụ:
    • Môi trường local: http://localhost:3000/api/auth/callback/google
    • Môi trường production: [https://tenmien.com/api/auth/callback/google](https://tenmien.com/api/auth/callback/google)
  5. Lưu lại hai chuỗi quan trọng: Client ID (công khai) và Client Secret (tuyệt đối giữ bí mật).

3. Dựng Endpoint trao đổi mã tại Backend

Dưới đây là ví dụ xử lý việc nhận Authorization Code và đổi lấy profile người dùng bằng Node.js / Express:

// Endpoint callback nhận redirect từ Google
app.get('/api/auth/callback/google', async (req, res) => {
  const { code } = req.query;

  if (!code) {
    return res.status(400).send('Không tìm thấy mã xác thực');
  }

  try {
    // 1. Gửi code lên Google để đổi lấy Token
    const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        code: String(code),
        client_id: process.env.GOOGLE_CLIENT_ID,
        client_secret: process.env.GOOGLE_CLIENT_SECRET,
        redirect_uri: process.env.GOOGLE_REDIRECT_URI,
        grant_type: 'authorization_code',
      }),
    });

    const tokens = await tokenResponse.json();

    // 2. Dùng access_token để lấy thông tin hồ sơ
    const userResponse = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
      headers: { Authorization: `Bearer ${tokens.access_token}` },
    });

    const profile = await userResponse.json();

    // 3. Tìm hoặc tạo user trong cơ sở dữ liệu
    let user = await db.user.findUnique({ where: { email: profile.email } });
    if (!user) {
      user = await db.user.create({
        data: {
          email: profile.email,
          name: profile.name,
          avatar: profile.picture,
          provider: 'google',
        },
      });
    }

    // 4. Khởi tạo session nội bộ của hệ thống (HTTP-only Cookie)
    setSessionCookie(res, user.id);
    return res.redirect('/dashboard');
  } catch (error) {
    console.error('Lỗi xác thực OAuth:', error);
    return res.redirect('/login?error=oauth_failed');
  }
});

4. 5 lỗi bảo mật thường gặp khi làm OAuth 2.0

  • Quên tham số state (Nguy cơ CSRF): Luôn gửi kèm một chuỗi ngẫu nhiên state khi chuyển hướng sang Google và kiểm tra lại giá trị này lúc nhận callback để tránh tấn công giả mạo yêu cầu.
  • Lộ Client Secret ở Frontend: Client Secret chỉ được xuất hiện trong code backend hoặc file .env phía server. Tuyệt đối không nhúng vào mã nguồn React hay ứng dụng mobile.
  • Lưu Access Token vào LocalStorage: Token lưu trong LocalStorage rất dễ bị tấn công qua lỗi XSS. Hãy dùng session cookie gắn cờ HttpOnly, Secure và SameSite=Lax.
  • Tin tưởng dữ liệu chưa xác thực: Nếu bạn decode id_token bằng thư viện JSON thông thường thay vì verify chữ ký thông qua SDK của Google, kẻ tấn công có thể chỉnh sửa nội dung bên trong token.
  • Không đồng bộ tài khoản trùng email: Nếu user từng đăng ký bằng email/mật khẩu, sau đó bấm đăng nhập bằng Google với cùng email đó, cần kiểm tra cờ email_verified: true từ Google trước khi tiến hành gộp tài khoản.

Leave a Reply

Your email address will not be published. Required fields are marked *