0

[Series] Xây dựng RESTful API từ con số 0 với PHP Thuần & MVC - Phần 10: Hoàn thiện Phân quyền - Gán Vai trò cho Người dùng

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

Sau khi đã có một hệ thống Roles và Permissions đồ sộ, bước cuối cùng là phải có một "vị vua" (Admin) có quyền ban phát các vai trò đó cho thần dân (Users). Trong bài viết này, chúng ta sẽ xây dựng Endpoint PUT /api/users/{id}/role.

Tại sao lại dùng PUT? Vì hành động này về bản chất là cập nhật (Update) một thuộc tính cụ thể của User. Hãy cùng xem cách chúng ta triển khai nó một cách chuyên nghiệp trong mô hình MVC nhé.

1. Tầng Model: Logic Gán vai trò (User.php)

Chúng ta cần một phương thức để thực hiện việc cập nhật role_id cho User. Để đảm bảo tính toàn vẹn dữ liệu, chúng ta phải kiểm tra xem role_id đó có thực sự tồn tại trong hệ thống hay không trước khi gán.

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

<?php
namespace App\Models;

// ... (Các phương thức cũ)

    /**
     * Gán hoặc thay đổi vai trò cho một User
     */
    public function assignRole($userId, $roleId) {
        // 1. Kiểm tra Role có tồn tại trong Database không
        $stmt = $this->db->prepare("SELECT id FROM Roles WHERE id = ?");
        $stmt->execute([$roleId]);
        
        if (!$stmt->fetch()) {
            return false; // Role không tồn tại
        }

        // 2. Cập nhật role_id cho User
        $stmt = $this->db->prepare("UPDATE Users SET role_id = ?, updated_at = NOW() WHERE id = ?");
        return $stmt->execute([$roleId, $userId]);
    }

2. Tầng Controller: Tiếp nhận và Xử lý (UserController.php)

Controller sẽ đóng vai trò kiểm duyệt dữ liệu đầu vào và gọi Model thực thi. Đây cũng là nơi chúng ta sẽ tích hợp các tầng bảo mật ở bước sau.

File: app/Controllers/UserController.php (Bổ sung method)

<?php
namespace App\Controllers;

use App\Core\Response;
use App\Models\User;
use App\Middleware\AuthMiddleware;

class UserController
{
    // ... (Các hàm updateProfile, changePassword đã viết ở phần trước)

    public function assignRole($userId) {
        // 1. Lấy dữ liệu từ Request Body
        $data = json_decode(file_get_contents("php://input"), true);
        
        if (empty($data['role_id'])) {
            Response::json(['error' => 'Vui lòng cung cấp role_id'], 422);
        }

        $userModel = new User();
        
        // 2. Thực hiện gán vai trò qua Model
        $success = $userModel->assignRole($userId, $data['role_id']);

        if (!$success) {
            Response::json(['error' => 'role_id không hợp lệ hoặc không tìm thấy người dùng'], 404);
        }

        Response::json(['message' => 'Đã cập nhật vai trò cho người dùng thành công!']);
    }
}

3. Cấu hình Route động (index.php)

Chúng ta tiếp tục sử dụng Regex để bắt ID của người dùng từ URL.

File: public/index.php

// ... Autoload & Imports ...
$userController = new UserController();

// ... Các route cũ ...

// API Gán vai trò cho User: /api/users/{id}/role
if (preg_match('#^/api/users/(\d+)/role$#', $uri, $matches) && $method === 'PUT') {
    $userId = $matches[1];
    
    /**
     * MẸO BẢO MẬT:
     * Bạn nên thêm PermissionMiddleware::check('assign_role'); ở đây 
     * để đảm bảo chỉ những ai có quyền "assign_role" mới thực hiện được hành động này.
     */
    
    $userController->assignRole($userId);
}

4. Kiểm thử tính năng (Test with Curl)

Giả sử bạn muốn gán vai trò Admin (ID: 1) cho người dùng có ID: 5.

curl -X PUT http://localhost:8000/api/users/5/role \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer {JWT_TOKEN_ADMIN}" \
  -d '{"role_id": 1}'

Góc nhìn chuyên gia: Những lưu ý quan trọng

Chỉ Admin mới được gán quyền: Đây là lỗi sơ đẳng nhất. Nếu bạn không dùng PermissionMiddleware để bảo vệ route này, bất kỳ người dùng nào có Token cũng có thể tự biến mình thành Admin.

Audit Logs (Ghi nhật ký): Trong các dự án thực tế lớn, việc thay đổi vai trò cực kỳ nhạy cảm. Bạn nên tạo một bảng User_Role_Logs để ghi lại: Ai đã gán role gì cho ai, vào lúc nào.

Vô hiệu hóa Token: Khi một User được thay đổi quyền hạn (ví dụ bị hạ cấp từ Admin xuống Member), Token JWT cũ của họ vẫn còn hiệu lực đến khi hết hạn (Exp). Bạn có thể cần một cơ chế để "Blacklist" Token cũ hoặc yêu cầu User đăng nhập lại để cập nhật quyền mới.

Lời kết cho Series "Xây dựng API thuần" Chúc mừng bạn đã hoàn thành chặng đường 10 phần đầy thử thách! Từ một thư mục trống, chúng ta đã xây dựng được một hệ thống API mạnh mẽ:

✅ Kiến trúc MVC sạch sẽ.

✅ Xác thực JWT bảo mật.

✅ Gửi Mail qua SMTP thực thụ.

✅ Hệ thống phân quyền RBAC (Roles & Permissions) hoàn chỉnh.

Hy vọng series này giúp các bạn hiểu rõ bản chất "những gì diễn ra dưới nắp ca-pô" của các Framework lớn như Laravel.


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í