0

[Series] Xây dựng RESTful API từ con số 0 với PHP Thuần & MVC - Phần 11: Tích hợp Đăng nhập bằng Google (Google OAuth2)

Chào các bạn, mình đã trở lại!

Trong thời đại mà trải nghiệm người dùng (UX) là vua, việc bắt khách hàng điền form đăng ký dài dằng dặc là một "điểm trừ" lớn. Social Login (SSO) giúp giải quyết vấn đề đó. Tuy nhiên, dưới góc độ Backend, chúng ta cần xử lý xác thực từ phía Google một cách cực kỳ cẩn thận để tránh bị giả mạo Token.

1. Chuẩn bị "Vũ khí"

Bước 1: Google Developer Console Bạn cần truy cập vào Google Cloud Console, tạo một Project và tạo OAuth 2.0 Client ID. Tại đây bạn sẽ nhận được:

  • Client ID
  • Client Secret

Bước 2: Cài đặt thư viện chính chủ Chúng ta sẽ dùng thư viện do Google cung cấp để việc xác thực id_token trở nên đơn giản và an toàn nhất.

composer require google/apiclient:^2.0

2. Cấu hình Biến môi trường (.env)

Đừng quên thêm Client ID của bạn vào file .env để Controller có thể sử dụng.

File: .env

GOOGLE_CLIENT_ID=your_google_client_id.apps.googleusercontent.com
JWT_SECRET=your_super_secret_key

3. Tầng Model: Hỗ trợ Người dùng Google

Chúng ta cần cập nhật User.php để xử lý các tình huống: Tìm user qua Google ID, tìm qua Email (để liên kết tài khoản) hoặc tạo mới hoàn toàn từ dữ liệu Google trả về.

File: app/Models/User.php (Bổ sung)

<?php
namespace App\Models;

use App\Core\Database;

class User {
    protected $db;

    public function __construct() {
        $this->db = Database::getInstance();
    }

    /**
     * Tìm User bằng Google ID độc nhất
     */
    public function findByGoogleId($googleId) {
        $stmt = $this->db->prepare("SELECT * FROM Users WHERE google_id = ?");
        $stmt->execute([$googleId]);
        return $stmt->fetch();
    }

    /**
     * Tạo mới User từ thông tin Google cung cấp
     */
    public function createFromGoogle($data) {
        $stmt = $this->db->prepare("
            INSERT INTO Users (name, email, google_id, avatar, status, created_at)
            VALUES (?, ?, ?, ?, 'active', NOW())
        ");
        $stmt->execute([
            $data['name'],
            $data['email'],
            $data['google_id'],
            $data['avatar']
        ]);
        return $this->db->lastInsertId();
    }
}

4. Tầng Controller: Xử lý Xác thực Google

Đây là "trái tim" của tính năng này. Server sẽ nhận id_token từ phía Client (Frontend), sau đó gửi yêu cầu xác thực sang Google Server.

File: app/Controllers/GoogleAuthController.php

<?php
namespace App\Controllers;

use App\Core\Response;
use App\Models\User;
use Firebase\JWT\JWT;
use Google_Client;

class GoogleAuthController
{
    public function loginWithGoogle() {
        // 1. Nhận id_token từ Client
        $input = json_decode(file_get_contents("php://input"), true);

        if (empty($input['id_token'])) {
            Response::json(['error' => 'Thiếu id_token từ phía Google'], 422);
        }

        // 2. Cấu hình Google Client để xác thực Token
        $client = new Google_Client(['client_id' => $_ENV['GOOGLE_CLIENT_ID']]);

        try {
            // Xác thực token xem có phải do Google phát hành không
            $payload = $client->verifyIdToken($input['id_token']);
            
            if (!$payload) {
                Response::json(['error' => 'Token Google không hợp lệ hoặc đã hết hạn'], 401);
            }

            // 3. Trích xuất thông tin người dùng
            $googleId = $payload['sub']; // ID định danh duy nhất của Google
            $email = $payload['email'];
            $name = $payload['name'] ?? '';
            $avatar = $payload['picture'] ?? '';

            $userModel = new User();
            $user = $userModel->findByGoogleId($googleId);

            // 4. Logic xử lý User (Tạo mới hoặc Liên kết)
            if (!$user) {
                // Kiểm tra xem email này đã tồn tại trong hệ thống chưa (đăng ký bằng pass trước đó)
                $existing = $userModel->findByEmail($email);
                
                if ($existing) {
                    // Nếu đã có email → Gắn thêm google_id và avatar mới nhất
                    $stmt = $this->db->prepare("UPDATE Users SET google_id = ?, avatar = ? WHERE email = ?");
                    $stmt->execute([$googleId, $avatar, $email]);
                    $user = $userModel->findByEmail($email);
                } else {
                    // Nếu chưa có → Tạo mới hoàn toàn
                    $userId = $userModel->createFromGoogle([
                        'name' => $name,
                        'email' => $email,
                        'google_id' => $googleId,
                        'avatar' => $avatar
                    ]);
                    $user = $userModel->findById($userId);
                }
            }

            // 5. Phát hành JWT "nhà làm" để User truy cập các API khác
            $jwt = JWT::encode([
                'sub' => $user['id'],
                'email' => $user['email'],
                'iat' => time(),
                'exp' => time() + 3600 // Hết hạn sau 1 giờ
            ], $_ENV['JWT_SECRET'], 'HS256');

            Response::json([
                'message' => 'Đăng nhập Google thành công!',
                'access_token' => $jwt,
                'user' => [
                    'id' => $user['id'],
                    'name' => $user['name'],
                    'avatar' => $user['avatar']
                ]
            ]);

        } catch (\Exception $e) {
            Response::json(['error' => 'Xác thực Google thất bại', 'details' => $e->getMessage()], 500);
        }
    }
}

5. Cấu hình Route (index.php)

Thêm điểm cuối (Endpoint) để Client có thể gửi yêu cầu đăng nhập.

File: public/index.php

use App\Controllers\GoogleAuthController;

$googleController = new GoogleAuthController();

if ($uri === '/api/auth/google' && $method === 'POST') {
    $googleController->loginWithGoogle();
}

6. Kiểm thử API

Để test API này, bạn cần có một id_token hợp lệ (thường lấy từ thư viện Google Login trên React/Vue/Mobile). Sau đó, dùng curl để gửi yêu cầu:

curl -X POST http://localhost:8000/api/auth/google \
  -H "Content-Type: application/json" \
  -d '{"id_token": "CHUỖI_ID_TOKEN_NHẬN_TỪ_GOOGLE_CLIENT"}'

Tạm kết cho Phần 11

Vậy là chúng ta đã hoàn thành việc tích hợp Google Login – một tính năng "must-have" cho mọi ứng dụng hiện đại.

Hãy để lại bình luận phía dưới nhé! Đừng quên Upvote để ủng hộ "bố đời" ra thêm nhiều bài viết chất lượng. Chúc các bạn code vui vẻ!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí