PHP Classes

File: docs/BUG_FIX_MISSING_EXIT.md

Recommend this page to a friend!
  Packages of Adrian M   upMVC   docs/BUG_FIX_MISSING_EXIT.md   Download  
File: docs/BUG_FIX_MISSING_EXIT.md
Role: Auxiliary data
Content type: text/markdown
Description: Auxiliary data
Class: upMVC
Pure PHP web development without other frameworks
Author: By
Last change: Update of docs/BUG_FIX_MISSING_EXIT.md
Date: 2 months ago
Size: 5,813 bytes
 

Contents

Class file image Download

Critical Bug #3: Missing exit After Header Redirect

The Mystery

Date: October 16, 2025 - 18:48

The Confusing Log

[18:48:44] DEBUG Auth Controller - Redirecting to intended: /upMVC/moda ?
[18:48:44] DEBUG Router - reqRoute: /                                   ? WTF?!

The controller says "I'm redirecting to /upMVC/moda" but the user ends up at home /!

Root Cause: PHP Doesn't Stop After header()

The Misconception

Many developers think this stops execution:

header("Location: /somewhere");
// Code continues executing! ?

The Reality

header() only QUEUES a header - it doesn't stop PHP execution!

header("Location: /upMVC/moda");
// PHP keeps running...
// More code executes...
// More headers can be sent...
header("Location: /");  // This OVERWRITES the first one! ?

The Bug in Our Code

The Flow

// User POSTs login form
display() 
  ? case "/auth": $this->auth()
    ? auth() checks: logged === true? NO
      ? Calls $this->login()
        ? login() validates credentials
          ? Sets $_SESSION['logged'] = true
          ? Sends header("Location: /upMVC/moda")
          ? ? Returns to auth() ? BUG!
    ? auth() continues executing ?
    ? Now logged === true!
    ? Sends header("Location: /") ? OVERWRITES!

Visual Representation

auth() method
?
?? if (logged === true) {
?    header("Location: /");  ? SECOND REDIRECT (overwrites!)
?  }
?  else {
?    ?? login() {
?    ?    Set logged = true
?    ?    header("Location: /upMVC/moda");  ? FIRST REDIRECT
?    ?    return;  ? Goes back to auth()!
?    ?  }
?    ?? // auth() continues here! ?
?  }
?? // End of auth()

Why This Happens

Multiple Header() Calls

PHP allows multiple header() calls. The last one wins:

header("Location: /first");   // Queued
header("Location: /second");  // Queued (overwrites first)
header("Location: /third");   // Queued (overwrites second)
// When response is sent, only /third is used!

Our Specific Case

  1. First redirect in `login()`: `header("Location: /upMVC/moda")`
  2. `login()` returns
  3. `auth()` continues executing
  4. Now `$_SESSION['logged'] === true`
  5. Second redirect in `auth()`: `header("Location: /")`
  6. Second one overwrites the first! ?

The Fix

Add exit after EVERY header("Location: ..."):

Fix #1: In auth() method

private function auth()
{
    if (isset($_SESSION["logged"]) && $_SESSION["logged"] === true) {
        $intendedUrl = $_SESSION['intended_url'] ?? null;
        if ($intendedUrl) {
            unset($_SESSION['intended_url']);
            header("Location: $intendedUrl");
            exit;  // ? STOP HERE!
        } else {
            $this->url = BASE_URL;
            header("Location: $this->url");
            exit;  // ? STOP HERE!
        }
    } else {
        $this->login();
    }
}

Fix #2: In login() method

private function login()
{
    // ... login logic ...
    
    if ($loginSuccessful) {
        $_SESSION['logged'] = true;
        header("Location: " . $redirectUrl);
        exit;  // ? STOP HERE!
    }
}

Why exit is Critical

Without exit

header("Location: /target");
// Code keeps running...
echo "Some output";  // Might prevent redirect!
header("X-Custom: value");  // Can still modify headers!
header("Location: /other");  // Can overwrite redirect!

With exit

header("Location: /target");
exit;  // ? Stops ALL further execution
// Nothing below this line will execute

Testing the Fix

Before Fix

[18:48:44] Redirecting to intended: /upMVC/moda
[18:48:44] reqRoute: /  ? Wrong!

After Fix

[timestamp] Redirecting to intended: /upMVC/moda
[timestamp] reqRoute: /moda  ? Correct! ?

PHP Best Practices

Always Use exit After Redirects

// ? BAD
header("Location: /somewhere");

// ? GOOD
header("Location: /somewhere");
exit;

// ? ALSO GOOD
header("Location: /somewhere");
die();  // die() is alias for exit()

Or Use a Helper Function

function redirect(string $url): void
{
    header("Location: $url");
    exit;
}

// Usage:
redirect('/upMVC/moda');  // Automatically exits

Security Implications

Without exit, sensitive code might execute:

if (!$authenticated) {
    header("Location: /login");
    // ? Missing exit!
}

// This code WILL execute even for non-authenticated users! ?
$secretData = getConfidentialData();
echo json_encode($secretData);

With exit, code is safe:

if (!$authenticated) {
    header("Location: /login");
    exit;  // ? Stops here
}

// This code will NOT execute for non-authenticated users ?
$secretData = getConfidentialData();
echo json_encode($secretData);

Summary of All 3 Bugs

Bug #1: AuthMiddleware Overwriting Session

Problem: Middleware overwrote intended_url on every request Fix: Only store if not already set

Bug #2: Assignment Instead of Comparison

Problem: if ($logged = true) always true Fix: Use if ($logged === true)

Bug #3: Missing exit After Redirect (THIS BUG)

Problem: Code continues after header(), sending multiple redirects Fix: Add exit after every header("Location: ...")

Files Changed

  • `d:\GitHub\upMVC\modules\auth\Controller.php` - Added `exit` after header in `auth()` method (2 places) - Added `exit` after header in `login()` method (1 place)

The Lesson

> header() does NOT stop execution. Always use exit or die() after redirect headers!

This is one of the most common bugs in PHP applications and a frequent source of security vulnerabilities.