Protected Pages & Authorization
Protected Pages & Authorization
Now that we have registration and login systems, we need to protect certain pages from unauthorized access and implement role-based authorization. In this lesson, we'll learn how to create protected pages, implement access control, and manage user permissions.
Creating a Protected Page
Let's create a middleware system to protect pages that require authentication:
<?php
/**
* Authentication Middleware
* Include this at the top of any protected page
*/
// Start session if not already started
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
/**
* Require authentication
* Redirects to login page if user is not logged in
*/
function requireAuth() {
if (!isset($_SESSION['user_id'])) {
// Store the requested URL to redirect back after login
$_SESSION['redirect_after_login'] = $_SERVER['REQUEST_URI'];
// Redirect to login page
header('Location: /login.php');
exit;
}
// Check session validity
validateSession();
}
/**
* Validate session security
*/
function validateSession() {
// Check session age (expire after 2 hours of inactivity)
if (isset($_SESSION['last_activity'])) {
$inactive_time = time() - $_SESSION['last_activity'];
if ($inactive_time > 7200) { // 2 hours
session_unset();
session_destroy();
header('Location: /login.php?session_expired=1');
exit;
}
}
// Update last activity time
$_SESSION['last_activity'] = time();
// Validate user agent
$current_ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
if (isset($_SESSION['user_agent'])) {
if ($_SESSION['user_agent'] !== $current_ua) {
session_unset();
session_destroy();
header('Location: /login.php?security_error=1');
exit;
}
} else {
$_SESSION['user_agent'] = $current_ua;
}
}
/**
* Get current user information
*/
function getCurrentUser() {
return [
'id' => $_SESSION['user_id'] ?? null,
'username' => $_SESSION['username'] ?? null,
'email' => $_SESSION['email'] ?? null,
'full_name' => $_SESSION['full_name'] ?? null,
'role' => $_SESSION['role'] ?? 'user'
];
}
/**
* Check if user is logged in
*/
function isLoggedIn() {
return isset($_SESSION['user_id']);
}
?>
Using the Authentication Middleware
Here's how to protect a page using the middleware:
<?php
// Include authentication middleware
require_once 'auth_middleware.php';
// Require authentication - redirects if not logged in
requireAuth();
// Get current user
$user = getCurrentUser();
// Database connection
require_once 'db_connect.php';
// Fetch user statistics or data
$stmt = $pdo->prepare('
SELECT COUNT(*) as total_logins
FROM login_attempts
WHERE username = ? AND success = 1
');
$stmt->execute([$user['username']]);
$stats = $stmt->fetch(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background: #f5f5f5;
}
.navbar {
background: #333;
color: white;
padding: 15px 30px;
display: flex;
justify-content: space-between;
align-items: center;
}
.navbar h2 {
margin: 0;
}
.navbar a {
color: white;
text-decoration: none;
margin-left: 20px;
padding: 8px 16px;
border-radius: 4px;
transition: background 0.3s;
}
.navbar a:hover {
background: #555;
}
.container {
max-width: 1200px;
margin: 40px auto;
padding: 0 20px;
}
.welcome-card {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
margin-bottom: 30px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
}
.stat-card {
background: white;
padding: 25px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.stat-card h3 {
color: #667eea;
margin: 0 0 10px 0;
font-size: 14px;
text-transform: uppercase;
}
.stat-card .value {
font-size: 32px;
font-weight: bold;
color: #333;
}
</style>
</head>
<body>
<nav class="navbar">
<h2>My Dashboard</h2>
<div>
<a href="profile.php">Profile</a>
<a href="settings.php">Settings</a>
<a href="logout.php">Logout</a>
</div>
</nav>
<div class="container">
<div class="welcome-card">
<h1>Welcome back, <?php echo htmlspecialchars($user['full_name']); ?>!</h1>
<p>Email: <?php echo htmlspecialchars($user['email']); ?></p>
</div>
<div class="stats-grid">
<div class="stat-card">
<h3>Total Logins</h3>
<div class="value"><?php echo $stats['total_logins']; ?></div>
</div>
<div class="stat-card">
<h3>Account Status</h3>
<div class="value">Active</div>
</div>
<div class="stat-card">
<h3>Member Since</h3>
<div class="value">2024</div>
</div>
</div>
</div>
</body>
</html>
Role-Based Authorization
Let's implement a role-based access control system to manage different user permissions:
ALTER TABLE users
ADD COLUMN role ENUM('user', 'admin', 'moderator') DEFAULT 'user' AFTER is_active;
<?php
/**
* Authorization Functions
* Include this for role-based access control
*/
require_once 'auth_middleware.php';
/**
* Get user role from database
*/
function getUserRole($pdo, $user_id) {
$stmt = $pdo->prepare('SELECT role FROM users WHERE id = ?');
$stmt->execute([$user_id]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result['role'] ?? 'user';
}
/**
* Require specific role
* Redirects with error if user doesn't have required role
*/
function requireRole($required_role, $pdo) {
requireAuth(); // First check authentication
$user_id = $_SESSION['user_id'];
$user_role = getUserRole($pdo, $user_id);
// Store role in session for future checks
$_SESSION['role'] = $user_role;
$role_hierarchy = [
'user' => 1,
'moderator' => 2,
'admin' => 3
];
$user_level = $role_hierarchy[$user_role] ?? 0;
$required_level = $role_hierarchy[$required_role] ?? 0;
if ($user_level < $required_level) {
// User doesn't have sufficient permissions
http_response_code(403);
die('<h1>403 Forbidden</h1><p>You do not have permission to access this page.</p>');
}
}
/**
* Check if user has specific role
*/
function hasRole($role, $pdo) {
if (!isLoggedIn()) {
return false;
}
$user_id = $_SESSION['user_id'];
$user_role = $_SESSION['role'] ?? getUserRole($pdo, $user_id);
$role_hierarchy = [
'user' => 1,
'moderator' => 2,
'admin' => 3
];
$user_level = $role_hierarchy[$user_role] ?? 0;
$required_level = $role_hierarchy[$role] ?? 0;
return $user_level >= $required_level;
}
/**
* Check if user can perform action
* Define custom permissions for specific actions
*/
function can($action, $pdo) {
if (!isLoggedIn()) {
return false;
}
$user_role = $_SESSION['role'] ?? getUserRole($pdo, $_SESSION['user_id']);
// Define permissions
$permissions = [
'user' => [
'view_profile',
'edit_own_profile',
'view_dashboard'
],
'moderator' => [
'view_profile',
'edit_own_profile',
'view_dashboard',
'moderate_content',
'view_reports'
],
'admin' => [
'view_profile',
'edit_own_profile',
'view_dashboard',
'moderate_content',
'view_reports',
'manage_users',
'view_admin_panel',
'edit_site_settings'
]
];
$user_permissions = $permissions[$user_role] ?? [];
return in_array($action, $user_permissions);
}
?>
Admin-Only Page Example
Create a page that only administrators can access:
<?php
require_once 'authorization.php';
require_once 'db_connect.php';
// Require admin role
requireRole('admin', $pdo);
// Get current user
$user = getCurrentUser();
// Fetch all users (admin function)
$stmt = $pdo->query('
SELECT id, username, email, full_name, role, is_active, created_at, last_login
FROM users
ORDER BY created_at DESC
');
$all_users = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Panel</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background: #f5f5f5;
}
.navbar {
background: #d32f2f;
color: white;
padding: 15px 30px;
display: flex;
justify-content: space-between;
align-items: center;
}
.container {
max-width: 1400px;
margin: 40px auto;
padding: 0 20px;
}
.panel-header {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
margin-bottom: 30px;
}
table {
width: 100%;
background: white;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
th, td {
padding: 15px;
text-align: left;
border-bottom: 1px solid #eee;
}
th {
background: #667eea;
color: white;
font-weight: bold;
}
.role-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: bold;
}
.role-admin {
background: #ffebee;
color: #c62828;
}
.role-moderator {
background: #e3f2fd;
color: #1565c0;
}
.role-user {
background: #f3e5f5;
color: #6a1b9a;
}
.status-active {
color: #2e7d32;
font-weight: bold;
}
.status-inactive {
color: #c62828;
font-weight: bold;
}
</style>
</head>
<body>
<nav class="navbar">
<h2>Admin Panel</h2>
<div>
<a href="dashboard.php" style="color: white; text-decoration: none; margin-right: 20px;">Dashboard</a>
<a href="logout.php" style="color: white; text-decoration: none;">Logout</a>
</div>
</nav>
<div class="container">
<div class="panel-header">
<h1>User Management</h1>
<p>Total Users: <?php echo count($all_users); ?></p>
</div>
<table>
<thead>
<tr>
<th>ID</th>
<th>Username</th>
<th>Email</th>
<th>Full Name</th>
<th>Role</th>
<th>Status</th>
<th>Created</th>
<th>Last Login</th>
</tr>
</thead>
<tbody>
<?php foreach ($all_users as $u): ?>
<tr>
<td><?php echo $u['id']; ?></td>
<td><?php echo htmlspecialchars($u['username']); ?></td>
<td><?php echo htmlspecialchars($u['email']); ?></td>
<td><?php echo htmlspecialchars($u['full_name']); ?></td>
<td>
<span class="role-badge role-<?php echo $u['role']; ?>">
<?php echo strtoupper($u['role']); ?>
</span>
</td>
<td class="<?php echo $u['is_active'] ? 'status-active' : 'status-inactive'; ?>">
<?php echo $u['is_active'] ? 'Active' : 'Inactive'; ?>
</td>
<td><?php echo date('M d, Y', strtotime($u['created_at'])); ?></td>
<td><?php echo $u['last_login'] ? date('M d, Y H:i', strtotime($u['last_login'])) : 'Never'; ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</body>
</html>
Conditional UI Based on Permissions
Show or hide UI elements based on user permissions:
<?php
require_once 'authorization.php';
require_once 'db_connect.php';
requireAuth();
$user = getCurrentUser();
?>
<!-- Navigation menu with conditional links -->
<nav>
<a href="dashboard.php">Dashboard</a>
<?php if (can('moderate_content', $pdo)): ?>
<a href="moderation.php">Moderation</a>
<?php endif; ?>
<?php if (can('view_admin_panel', $pdo)): ?>
<a href="admin.php">Admin Panel</a>
<?php endif; ?>
<a href="logout.php">Logout</a>
</nav>
<!-- Conditional content display -->
<?php if (hasRole('admin', $pdo)): ?>
<div class="admin-notice">
<p>You have administrator privileges</p>
</div>
<?php endif; ?>
API Endpoint Protection
Protect AJAX endpoints and API routes:
<?php
header('Content-Type: application/json');
require_once '../authorization.php';
require_once '../db_connect.php';
// Require admin role
try {
requireRole('admin', $pdo);
} catch (Exception $e) {
http_response_code(403);
echo json_encode([
'success' => false,
'message' => 'Unauthorized access'
]);
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$user_id = $_POST['user_id'] ?? 0;
// Prevent admin from deleting themselves
if ($user_id == $_SESSION['user_id']) {
echo json_encode([
'success' => false,
'message' => 'Cannot delete your own account'
]);
exit;
}
// Delete user
try {
$stmt = $pdo->prepare('DELETE FROM users WHERE id = ?');
$stmt->execute([$user_id]);
echo json_encode([
'success' => true,
'message' => 'User deleted successfully'
]);
} catch (PDOException $e) {
echo json_encode([
'success' => false,
'message' => 'Failed to delete user'
]);
}
}
?>
Redirect After Login
Implement smart redirects to return users to their requested page after login:
// After successful login, check for redirect URL
if (isset($_SESSION['redirect_after_login'])) {
$redirect_url = $_SESSION['redirect_after_login'];
unset($_SESSION['redirect_after_login']);
header("Location: $redirect_url");
} else {
// Default redirect to dashboard
header('Location: dashboard.php');
}
exit;
- Create a protected dashboard page using the authentication middleware
- Add role column to users table and update registration to assign default role
- Create an admin panel that lists all users (admin-only access)
- Implement a moderator page with limited administrative functions
- Add permission-based UI conditionals to show/hide navigation items
- Create API endpoints with role-based protection
- Implement an access log to track who accesses protected pages
- Add a "sudo mode" feature requiring password re-entry for sensitive actions
Complete Security Checklist
- Authentication - Verify user identity with password_verify()
- Authorization - Check user permissions before allowing actions
- Session Security - Regenerate IDs, validate user agents, set timeouts
- Input Validation - Sanitize and validate all user inputs
- SQL Injection Prevention - Use prepared statements exclusively
- XSS Prevention - Escape output with htmlspecialchars()
- CSRF Protection - Implement CSRF tokens for forms
- Password Security - Hash with PASSWORD_DEFAULT, enforce strong passwords
- Rate Limiting - Prevent brute force with login attempt tracking
- HTTPS Only - Secure cookies, force SSL connections
- Logging - Track access, failed attempts, and security events
- Error Handling - Don't expose sensitive information in errors
Summary
You've now built a complete authentication and authorization system! You've learned to:
- Create secure user registration with validation and password hashing
- Implement login systems with session management and "Remember Me"
- Protect pages with authentication middleware
- Build role-based access control (RBAC) systems
- Implement permission-based authorization
- Secure API endpoints with role verification
- Follow security best practices throughout
These concepts form the foundation of web application security. As you build real applications, continue to stay updated on security best practices and common vulnerabilities (OWASP Top 10).