shield 11. Sistema de Permissões RBAC (Role-Based Access Control)

RBAC (Role-Based Access Control) é um dos pilares de segurança mais importantes em aplicações modernas. Em vez de verificar permissões usuário por usuário, você atribui papéis (roles) e cada papel tem um conjunto específico de permissões. Isso torna o sistema escalável, auditável e fácil de manter.

Imagine um sistema de cursos online: administradores podem criar/editar/deletar qualquer curso, moderadores podem apenas editar conteúdo e aprovar comentários, enquanto usuários comuns apenas visualizam e interagem. Tudo controlado automaticamente pelo papel atribuído.

info
Conceito Chave: No RBAC, permissões são associadas a papéis, não a usuários individuais. Um usuário pode ter múltiplos papéis, e cada papel define o que ele pode fazer.

account_tree Estrutura de Roles e Permissions

┌────────────────────────────────────────────────┐
│                 RBAC HIERARCHY                  │
└────────────────────────────────────────────────┘

┌─────────────┐      ┌──────────────────────────┐
│    USER     │─────▶│         ROLES            │
│  (Usuário)  │      │  - admin                 │
│             │      │  - moderator             │
│             │      │  - user                  │
│             │      │  - subscriber            │
└─────────────┘      └──────────┬───────────────┘
                                │
                                ▼
                     ┌──────────────────────────┐
                     │      PERMISSIONS         │
                     │  - courses.create        │
                     │  - courses.edit          │
                     │  - courses.delete        │
                     │  - courses.view          │
                     │  - users.manage          │
                     │  - comments.moderate     │
                     └──────────────────────────┘

Exemplo de Atribuição:
  admin       → ALL PERMISSIONS
  moderator   → courses.edit, comments.moderate
  user        → courses.view
  subscriber  → courses.view + premium_content.view

storage Estrutura de Banco de Dados

table_chart Tabelas Necessárias

Para implementar RBAC completo, você precisa de 4 tabelas principais:

  • users - Dados dos usuários
  • roles - Papéis disponíveis
  • permissions - Permissões do sistema
  • user_roles - Relacionamento N:N
  • role_permissions - Permissões por papel

policy Vantagens do RBAC

  • Escalabilidade: Adicione usuários sem tocar em permissões
  • Auditoria: Rastreie quem tem acesso a quê
  • Manutenção: Altere permissões de papel, não de usuários individuais
  • Compliance: Facilita adequação a normas (LGPD, ISO 27001)

code Implementação Completa: Database Schema

warning
Atenção: Execute este SQL no seu banco de dados. Ele criará todas as tabelas e alguns dados de exemplo.
-- Tabela de Roles (Papéis)
CREATE TABLE roles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL UNIQUE,
    description VARCHAR(255),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- Tabela de Permissions (Permissões)
CREATE TABLE permissions (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL UNIQUE,
    description VARCHAR(255),
    resource VARCHAR(50) NOT NULL, -- Ex: 'courses', 'users', 'comments'
    action VARCHAR(50) NOT NULL,   -- Ex: 'create', 'read', 'update', 'delete'
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_resource_action (resource, action)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- Tabela de relacionamento User -> Role (N:N)
CREATE TABLE user_roles (
    user_id INT NOT NULL,
    role_id INT NOT NULL,
    assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (user_id, role_id),
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- Tabela de relacionamento Role -> Permission (N:N)
CREATE TABLE role_permissions (
    role_id INT NOT NULL,
    permission_id INT NOT NULL,
    granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (role_id, permission_id),
    FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
    FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- Inserir Roles padrão
INSERT INTO roles (name, description) VALUES
('admin', 'Administrador com acesso total ao sistema'),
('moderator', 'Moderador que pode editar conteúdo e moderar comentários'),
('user', 'Usuário padrão com acesso básico'),
('subscriber', 'Assinante com acesso a conteúdos premium');

-- Inserir Permissions
INSERT INTO permissions (name, description, resource, action) VALUES
('courses.create', 'Criar novos cursos', 'courses', 'create'),
('courses.read', 'Visualizar cursos', 'courses', 'read'),
('courses.update', 'Editar cursos', 'courses', 'update'),
('courses.delete', 'Deletar cursos', 'courses', 'delete'),
('users.manage', 'Gerenciar usuários', 'users', 'manage'),
('comments.moderate', 'Moderar comentários', 'comments', 'moderate'),
('premium.access', 'Acessar conteúdo premium', 'premium', 'access');

-- Atribuir permissões aos roles
-- ADMIN: todas as permissões
INSERT INTO role_permissions (role_id, permission_id)
SELECT 1, id FROM permissions;

-- MODERATOR: editar cursos e moderar comentários
INSERT INTO role_permissions (role_id, permission_id)
SELECT 2, id FROM permissions WHERE name IN ('courses.read', 'courses.update', 'comments.moderate');

-- USER: apenas visualizar
INSERT INTO role_permissions (role_id, permission_id)
SELECT 3, id FROM permissions WHERE name = 'courses.read';

-- SUBSCRIBER: visualizar + premium
INSERT INTO role_permissions (role_id, permission_id)
SELECT 4, id FROM permissions WHERE name IN ('courses.read', 'premium.access');

settings Backend PHP - Classe RBAC

// classes/RBAC.php
namespace App\Services;

use PDO;

class RBAC {
    private $db;
    
    public function __construct(PDO $db) {
        $this->db = $db;
    }
    
    /**
     * Verifica se o usuário tem uma permissão específica
     */
    public function hasPermission(int $userId, string $permissionName): bool {
        $sql = "
            SELECT COUNT(*) as count
            FROM user_roles ur
            JOIN role_permissions rp ON ur.role_id = rp.role_id
            JOIN permissions p ON rp.permission_id = p.id
            WHERE ur.user_id = :user_id AND p.name = :permission
        ";
        
        $stmt = $this->db->prepare($sql);
        $stmt->execute([
            'user_id' => $userId,
            'permission' => $permissionName
        ]);
        
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        return $result['count'] > 0;
    }
    
    /**
     * Verifica se o usuário tem um papel específico
     */
    public function hasRole(int $userId, string $roleName): bool {
        $sql = "
            SELECT COUNT(*) as count
            FROM user_roles ur
            JOIN roles r ON ur.role_id = r.id
            WHERE ur.user_id = :user_id AND r.name = :role
        ";
        
        $stmt = $this->db->prepare($sql);
        $stmt->execute([
            'user_id' => $userId,
            'role' => $roleName
        ]);
        
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        return $result['count'] > 0;
    }
    
    /**
     * Retorna todos os papéis do usuário
     */
    public function getUserRoles(int $userId): array {
        $sql = "
            SELECT r.id, r.name, r.description
            FROM user_roles ur
            JOIN roles r ON ur.role_id = r.id
            WHERE ur.user_id = :user_id
        ";
        
        $stmt = $this->db->prepare($sql);
        $stmt->execute(['user_id' => $userId]);
        
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    
    /**
     * Retorna todas as permissões do usuário
     */
    public function getUserPermissions(int $userId): array {
        $sql = "
            SELECT DISTINCT p.name, p.description, p.resource, p.action
            FROM user_roles ur
            JOIN role_permissions rp ON ur.role_id = rp.role_id
            JOIN permissions p ON rp.permission_id = p.id
            WHERE ur.user_id = :user_id
        ";
        
        $stmt = $this->db->prepare($sql);
        $stmt->execute(['user_id' => $userId]);
        
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    
    /**
     * Atribui um papel a um usuário
     */
    public function assignRole(int $userId, int $roleId): bool {
        $sql = "INSERT IGNORE INTO user_roles (user_id, role_id) VALUES (:user_id, :role_id)";
        $stmt = $this->db->prepare($sql);
        return $stmt->execute([
            'user_id' => $userId,
            'role_id' => $roleId
        ]);
    }
    
    /**
     * Remove um papel de um usuário
     */
    public function revokeRole(int $userId, int $roleId): bool {
        $sql = "DELETE FROM user_roles WHERE user_id = :user_id AND role_id = :role_id";
        $stmt = $this->db->prepare($sql);
        return $stmt->execute([
            'user_id' => $userId,
            'role_id' => $roleId
        ]);
    }
    
    /**
     * Verifica múltiplas permissões (operador AND)
     */
    public function hasAllPermissions(int $userId, array $permissions): bool {
        foreach ($permissions as $permission) {
            if (!$this->hasPermission($userId, $permission)) {
                return false;
            }
        }
        return true;
    }
    
    /**
     * Verifica múltiplas permissões (operador OR)
     */
    public function hasAnyPermission(int $userId, array $permissions): bool {
        foreach ($permissions as $permission) {
            if ($this->hasPermission($userId, $permission)) {
                return true;
            }
        }
        return false;
    }
}

verified_user Middleware de Autorização

// middleware/AuthorizationMiddleware.php
namespace App\Middleware;

use App\Services\RBAC;

class AuthorizationMiddleware {
    private $rbac;
    
    public function __construct(RBAC $rbac) {
        $this->rbac = $rbac;
    }
    
    /**
     * Middleware que verifica permissão antes de executar a ação
     */
    public function requirePermission(string $permission) {
        return function($request, $response, $next) use ($permission) {
            $userId = $_SESSION['user_id'] ?? null;
            
            if (!$userId) {
                return $response->json([
                    'error' => 'Não autenticado'
                ], 401);
            }
            
            if (!$this->rbac->hasPermission($userId, $permission)) {
                return $response->json([
                    'error' => 'Permissão negada',
                    'required_permission' => $permission
                ], 403);
            }
            
            return $next($request, $response);
        };
    }
    
    /**
     * Middleware que verifica papel/role
     */
    public function requireRole(string $role) {
        return function($request, $response, $next) use ($role) {
            $userId = $_SESSION['user_id'] ?? null;
            
            if (!$userId) {
                return $response->json([
                    'error' => 'Não autenticado'
                ], 401);
            }
            
            if (!$this->rbac->hasRole($userId, $role)) {
                return $response->json([
                    'error' => 'Acesso negado',
                    'required_role' => $role
                ], 403);
            }
            
            return $next($request, $response);
        };
    }
}

// ===== USO EM ROTAS =====

// api/courses.php
require_once '../middleware/AuthorizationMiddleware.php';
require_once '../services/RBAC.php';

$rbac = new RBAC($db);
$authMiddleware = new AuthorizationMiddleware($rbac);

// Rota protegida por permissão
$router->post('/courses', function($req, $res) {
    // Apenas usuários com permissão 'courses.create' podem acessar
    $course = createCourse($req->body);
    return $res->json($course);
}, $authMiddleware->requirePermission('courses.create'));

// Rota protegida por role
$router->delete('/courses/:id', function($req, $res) {
    // Apenas admins podem deletar
    deleteCourse($req->params['id']);
    return $res->json(['success' => true]);
}, $authMiddleware->requireRole('admin'));

// Rota com verificação manual
$router->put('/courses/:id', function($req, $res) use ($rbac) {
    $userId = $_SESSION['user_id'];
    $courseId = $req->params['id'];
    
    // Admin pode editar tudo, moderador apenas se for o criador
    if ($rbac->hasRole($userId, 'admin')) {
        updateCourse($courseId, $req->body);
    } elseif ($rbac->hasPermission($userId, 'courses.update')) {
        if (isCourseOwner($userId, $courseId)) {
            updateCourse($courseId, $req->body);
        } else {
            return $res->json(['error' => 'Você só pode editar seus próprios cursos'], 403);
        }
    } else {
        return $res->json(['error' => 'Sem permissão'], 403);
    }
    
    return $res->json(['success' => true]);
});

code Frontend React - Hook usePermissions

// hooks/usePermissions.js
import { useState, useEffect } from 'react';
import axios from 'axios';

const API_URL = import.meta.env.VITE_API_URL;

export const usePermissions = () => {
    const [permissions, setPermissions] = useState([]);
    const [roles, setRoles] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        fetchUserPermissions();
    }, []);

    const fetchUserPermissions = async () => {
        try {
            const response = await axios.get(`${API_URL}/auth/permissions`, {
                withCredentials: true
            });
            
            setPermissions(response.data.permissions || []);
            setRoles(response.data.roles || []);
        } catch (error) {
            console.error('Erro ao buscar permissões:', error);
        } finally {
            setLoading(false);
        }
    };

    const hasPermission = (permission) => {
        return permissions.some(p => p.name === permission);
    };

    const hasRole = (role) => {
        return roles.some(r => r.name === role);
    };

    const hasAnyPermission = (perms) => {
        return perms.some(perm => hasPermission(perm));
    };

    const hasAllPermissions = (perms) => {
        return perms.every(perm => hasPermission(perm));
    };

    return {
        permissions,
        roles,
        loading,
        hasPermission,
        hasRole,
        hasAnyPermission,
        hasAllPermissions
    };
};

// ===== COMPONENTES DE AUTORIZAÇÃO =====

// components/ProtectedRoute.jsx
import { Navigate } from 'react-router-dom';
import { usePermissions } from '../hooks/usePermissions';

export const ProtectedRoute = ({ children, permission, role }) => {
    const { hasPermission, hasRole, loading } = usePermissions();

    if (loading) {
        return 
Carregando...
; } if (permission && !hasPermission(permission)) { return ; } if (role && !hasRole(role)) { return ; } return children; }; // components/Can.jsx - Componente para mostrar/ocultar elementos export const Can = ({ permission, role, children }) => { const { hasPermission, hasRole } = usePermissions(); if (permission && !hasPermission(permission)) { return null; } if (role && !hasRole(role)) { return null; } return children; }; // ===== EXEMPLO DE USO ===== // App.jsx import { BrowserRouter, Routes, Route } from 'react-router-dom'; import { ProtectedRoute } from './components/ProtectedRoute'; function App() { return ( } /> {/* Rota protegida por permissão */} } /> {/* Rota protegida por role */} } /> ); } // CourseCard.jsx - Mostrar botões condicionalmente import { Can } from './components/Can'; const CourseCard = ({ course }) => { return (

{course.title}

{course.description}

{/* Apenas usuários com permissão veem botão de editar */} {/* Apenas admins veem botão de deletar */}
); };

check_circle Best Practices e Segurança

✅ Faça Isso

  • Sempre verifique permissões no backend
  • Cache permissões do usuário em sessão (performance)
  • Use prepared statements em todas as queries RBAC
  • Registre mudanças de permissões em log de auditoria
  • Teste permissões com diferentes tipos de usuário
  • Use índices nas tabelas de relacionamento

❌ Evite Isso

  • Confiar apenas no frontend para controlar acesso
  • Hardcoded roles/permissions no código
  • Dar permissões demais "só por garantia"
  • Permitir que usuários escolham seus próprios papéis
  • Esquecer de revocar permissões ao remover usuários
  • Não ter um papel "superadmin" para emergências
rocket_launch
Próximo Passo: Com RBAC implementado, você tem controle total sobre quem pode fazer o quê. Na próxima seção, vamos integrar pagamentos PIX para monetizar sua aplicação!