2023 Application

Overview

GateArch is a full-stack web application providing a student information portal backed by a PHP 8 and MySQL server. Students log in with hashed credentials, view their enrolled courses, check grades, and access course details from a personalised dashboard. An admin panel provides CRUD operations for managing students, courses, and enrollments. The application is structured around a PHP routing layer served through Apache, with PDO prepared statements used for all database interactions to prevent SQL injection. Sessions handle authentication state, and role-based access control at the PHP level restricts admin routes to users with the admin role. The frontend is a responsive HTML/CSS/JavaScript interface that uses form-based interactions with server-side processing — no single-page framework, demonstrating a solid grasp of traditional multi-page application architecture.

Architecture

graph TD subgraph Frontend["Frontend (Browser)"] A["login.html\nCredential Form"] B["dashboard.php\nStudent: Courses + Grades"] C["admin.php\nAdmin: CRUD Panel"] D["courses.php\nCourse Listings"] end subgraph Backend["Backend (Apache + PHP 8)"] E["auth.php\nsession_start · password_verify\nRole Check"] F["students.php\nStudent CRUD\nPDO Queries"] G["courses.php\nCourse CRUD\nEnrollment Queries"] H["admin.php\nAdmin Route Guard\n\$_SESSION role check"] end subgraph Database["Database (MySQL)"] I["users\nid · username · password_hash · role"] J["courses\nid · name · credits · description"] K["enrollments\nstudent_id · course_id"] L["grades\nenrollment_id · grade · updated_at"] end A -->|"POST /login"| E E -->|"Session set"| B B -->|"PDO query"| G C -->|"Admin actions"| H H -->|"PDO CRUD"| F H -->|"PDO CRUD"| G E --> I F --> I G --> J G --> K G --> L style A fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style B fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style C fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style D fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style E fill:#181818,stroke:#1e1e1e,color:#888 style F fill:#181818,stroke:#1e1e1e,color:#888 style G fill:#181818,stroke:#1e1e1e,color:#888 style H fill:#181818,stroke:#1e1e1e,color:#888 style I fill:#1a1a2e,stroke:#00ff88,color:#e0e0e0 style J fill:#1a1a2e,stroke:#00ff88,color:#e0e0e0 style K fill:#1a1a2e,stroke:#00ff88,color:#e0e0e0 style L fill:#1a1a2e,stroke:#00ff88,color:#e0e0e0

Tech Stack

  • PHP 8 — Server-side routing, session management, role-based access control, form processing
  • MySQL — Relational database with four tables: users, courses, enrollments, grades; foreign key constraints enforce referential integrity
  • PDO (PHP Data Objects) — All database queries use prepared statements with bound parameters, eliminating SQL injection risk entirely
  • Apache — HTTP server; .htaccess used to restrict direct access to PHP include files
  • PHP Sessionssession_start() on every protected page; session data stores user ID and role; session is destroyed on logout
  • HTML5 / CSS3 / JavaScript — Responsive frontend; CSS Grid/Flexbox for dashboard layout; JavaScript for client-side form validation before POST submission

Build Process

1
Database Schema Design

Designed the relational schema to support the core features: user authentication and roles, course catalog, student-course enrollment, and grade recording. Foreign keys with ON DELETE CASCADE on the enrollments and grades tables ensure that deleting a student or course cleans up related records automatically.

CREATE TABLE users (
  id            INT AUTO_INCREMENT PRIMARY KEY,
  username      VARCHAR(80)  NOT NULL UNIQUE,
  password_hash VARCHAR(255) NOT NULL,
  role          ENUM('student','admin') NOT NULL DEFAULT 'student'
);

CREATE TABLE courses (
  id          INT AUTO_INCREMENT PRIMARY KEY,
  name        VARCHAR(120) NOT NULL,
  credits     TINYINT NOT NULL,
  description TEXT
);

CREATE TABLE enrollments (
  id         INT AUTO_INCREMENT PRIMARY KEY,
  student_id INT NOT NULL REFERENCES users(id)    ON DELETE CASCADE,
  course_id  INT NOT NULL REFERENCES courses(id)  ON DELETE CASCADE,
  UNIQUE KEY uq_enrollment (student_id, course_id)
);

CREATE TABLE grades (
  id            INT AUTO_INCREMENT PRIMARY KEY,
  enrollment_id INT NOT NULL REFERENCES enrollments(id) ON DELETE CASCADE,
  grade         DECIMAL(5,2),
  updated_at    TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
2
PHP Authentication with password_hash / password_verify

User passwords are never stored in plaintext. Registration uses password_hash($password, PASSWORD_DEFAULT) to store a bcrypt hash. Login uses password_verify($input, $stored_hash) to check the submission against the stored hash without ever decoding it. On successful verification, the user's ID and role are written to the session.

// auth.php — login processing
session_start();
$stmt = $pdo->prepare('SELECT id, password_hash, role FROM users WHERE username = ?');
$stmt->execute([$_POST['username']]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);

if ($user && password_verify($_POST['password'], $user['password_hash'])) {
    $_SESSION['user_id'] = $user['id'];
    $_SESSION['role']    = $user['role'];
    header('Location: ' . ($user['role'] === 'admin' ? 'admin.php' : 'dashboard.php'));
    exit;
}
// fallthrough: show login error
3
Student Dashboard with PDO Prepared Statements

The student dashboard queries enrolled courses and grades using a JOIN across three tables. All values are bound via PDO to prevent injection, including the session user ID that scopes the query to the authenticated student.

// dashboard.php — fetch enrolled courses with grades
$stmt = $pdo->prepare('
    SELECT c.name, c.credits, g.grade
    FROM enrollments e
    JOIN courses c ON c.id = e.course_id
    LEFT JOIN grades g ON g.enrollment_id = e.id
    WHERE e.student_id = ?
    ORDER BY c.name
');
$stmt->execute([$_SESSION['user_id']]);
$courses = $stmt->fetchAll(PDO::FETCH_ASSOC);
4
Admin Panel: Role-Gated CRUD Operations

Every admin-facing PHP file begins with a session role check. If the check fails, the user is redirected to the login page — there is no route that an unauthenticated or non-admin user can reach admin functionality through.

// admin.php — role guard (top of every admin file)
session_start();
if (!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') {
    header('Location: login.php');
    exit;
}

Admin CRUD operations use the same PDO prepared statement pattern. Form submissions are processed on POST, and the page redirects after successful operations (Post/Redirect/Get pattern) to prevent duplicate submissions on refresh.

5
Frontend: Responsive Layout & Client-Side Validation

The dashboard uses a CSS Grid layout with a sidebar for navigation and a main content area for course/grade tables. JavaScript validates form fields (non-empty, numeric grades in range 0–100) before submission, providing immediate feedback without a round-trip to the server. Server-side validation also runs on every POST as the authoritative check.

6
Security Hardening

Beyond PDO prepared statements, additional security measures were applied: CSRF tokens on all state-changing forms (generated with bin2hex(random_bytes(32)), stored in session, verified on POST), htmlspecialchars() wrapping all user-supplied data rendered into HTML output to prevent XSS, and .htaccess rules to deny direct HTTP access to PHP include and config files.

// CSRF token generation and embedding
$_SESSION['csrf'] = bin2hex(random_bytes(32));
// In form HTML:
// <input type="hidden" name="csrf" value="<?= $_SESSION['csrf'] ?>">

// CSRF verification on POST
if (!hash_equals($_SESSION['csrf'], $_POST['csrf'] ?? '')) {
    http_response_code(403); exit('CSRF validation failed');
}

Authentication & Request Flow

sequenceDiagram participant S as Student Browser participant P as PHP / Apache participant DB as MySQL S->>P: POST /login (username + password) P->>DB: SELECT password_hash WHERE username = ? DB-->>P: Row returned P->>P: password_verify(input, hash) P->>P: session_start() — write user_id + role P-->>S: 302 Redirect → dashboard.php S->>P: GET /dashboard.php (with session cookie) P->>P: session_start() — verify role P->>DB: SELECT courses + grades WHERE student_id = ? DB-->>P: Enrolled courses + grade rows P-->>S: Rendered HTML dashboard S->>P: POST /admin.php (add course) P->>P: Role check — admin only P->>P: Verify CSRF token P->>DB: INSERT INTO courses (name, credits, ...) DB-->>P: OK P-->>S: 302 Redirect → admin.php (PRG pattern)

Challenges & Solutions

Role-Based Routing Without a Framework. Without a routing framework like Laravel, every protected PHP file needed its own role check at the top. The pattern was standardised into a single require 'includes/auth-guard.php' include that accepts a required role parameter. This reduced the guard to a single line at the top of each protected file, making it easy to audit which files are protected and ensuring consistent behaviour.

SQL Injection Prevention. The most critical security concern in any PHP/MySQL application is SQL injection. Every query in the application uses PDO prepared statements with bound parameters — no string concatenation of user input into SQL anywhere in the codebase. This was enforced as a code review rule: any query using string interpolation was rejected and rewritten. PDO also enables proper error handling via exceptions rather than relying on the return value of mysql_query.

What I Learned

  • The full PHP session lifecycle: session_start(), writing to $_SESSION, reading it on subsequent requests, and destroying it on logout
  • password_hash() with PASSWORD_DEFAULT uses bcrypt with an automatically generated salt — the hash includes the algorithm identifier and salt, so password_verify() needs only the input and the stored hash
  • PDO prepared statements are the correct, non-negotiable approach to database queries with user input; string concatenation into SQL is never acceptable
  • CSRF protection requires generating a random token per session, embedding it in every state-changing form, and verifying it on POST before processing the request
  • The Post/Redirect/Get (PRG) pattern prevents duplicate form submissions on page refresh by redirecting after every successful POST operation
  • Relational schema design with foreign keys and ON DELETE CASCADE maintains referential integrity at the database level, independent of application logic
PHP MySQL HTML5 CSS JavaScript PDO Sessions Authentication Admin Panel