Unit 13 ยท E-commerce and Web-Related Components

13.4Privileges Management

Authentication answers "who are you?" (login). Authorisation answers "what are you allowed to do?" (privileges). Every request to a protected page must be checked on the server; hiding a button is not security.

Role-based access control (RBAC): each user has a role (customer, staff, admin); each page or action needs a minimum role.

ActionGuestCustomerStaffAdmin
Browse and search productsYesYesYesYes
Add to cartYesYesYesYes
Checkout, view own ordersNoYesYesYes
Add or edit productsNoNoYesYes
Change order statusNoNoYesYes
Manage users and rolesNoNoNoYes

includes/auth.php:

PHP
<?php
if (session_status() === PHP_SESSION_NONE) session_start();

function is_logged_in(): bool { return isset($_SESSION['user_id']); }

function current_role(): string { return $_SESSION['role'] ?? 'guest'; }

function require_login(): void {
    if (!is_logged_in()) {
        header('Location: /login.php?next=' . urlencode($_SERVER['REQUEST_URI']));
        exit;
    }
}

// Roles ranked by power
function role_level(string $role): int {
    return ['guest' => 0, 'customer' => 1, 'staff' => 2, 'admin' => 3][$role] ?? 0;
}

function require_role(string $minRole): void {
    require_login();
    if (role_level(current_role()) < role_level($minRole)) {
        http_response_code(403);
        exit('403 Forbidden: you do not have permission.');
    }
}

Use at the very top of each protected page:

PHP
<?php
require '../includes/auth.php';
require_role('staff');        // admin/products.php

Show only what the user may use (UX only, not security):

PHP
<?php if (role_level(current_role()) >= 2): ?>
  <a href='admin/products.php'>Manage products</a>
<?php endif; ?>

Ownership checks (prevent "IDOR"). A customer must see only their own orders even if they edit the URL to order.php?id=99:

PHP
if (current_role() === 'admin') {                    // admins may open any order
    $stmt = $pdo->prepare('SELECT * FROM orders WHERE id = ?');
    $stmt->execute([$_GET['id'] ?? 0]);
} else {                                             // customers only their own
    $stmt = $pdo->prepare('SELECT * FROM orders WHERE id = ? AND user_id = ?');
    $stmt->execute([$_GET['id'] ?? 0, $_SESSION['user_id']]);
}
$order = $stmt->fetch();
if (!$order) { http_response_code(404); exit('Order not found'); }

CSRF protection. Cross-site request forgery tricks a logged-in user's browser into sending a request they did not intend. Defend every state-changing form with a secret token:

PHP
<?php
// 1. When showing the form: create one secret token per session
if (empty($_SESSION['csrf'])) {
    $_SESSION['csrf'] = bin2hex(random_bytes(32));
}
?>
<form method='post' action='delete_product.php'>
  <input type='hidden' name='csrf' value='<?= $_SESSION['csrf'] ?>'>
  <input type='hidden' name='id' value='7'>
  <button>Delete product</button>
</form>

<?php
// 2. When handling the POST: reject any request without the right token
if (!hash_equals($_SESSION['csrf'] ?? '', $_POST['csrf'] ?? '')) {
    http_response_code(403);
    exit('Invalid request');
}

Principle of least privilege: give each user and each database account only what they need. The shop's database user should not be able to drop tables.