DownloadCritical Bug #4: JavaScript Redirect Overriding PHP Header
The Final Boss Bug
Date: October 16, 2025 - 18:53
The Stubborn Symptom
After fixing 3 bugs, the redirect STILL didn't work! The log showed:
[18:53:05] DEBUG Auth Controller - Redirecting to intended: /upMVC/moda ?
[18:53:05] DEBUG Router - reqRoute: / ? STILL WRONG!
Root Cause: Output Before Header
The Order of Operations Bug
// In login() method:
$_SESSION['logged'] = true;
$this->html->validateToken(); // ? Outputs HTML/JavaScript!
header("Location: /upMVC/moda"); // ? Too late! Headers already sent!
What validateToken() Does
public function validateToken()
{
?>
<script>
location.href = "<?php echo BASE_URL; ?>/"; // ? ALWAYS redirects to home!
</script>
<?php
}
The Deadly Sequence
-
PHP sets session: `$_SESSION['logged'] = true` ?
-
PHP calls: `$this->html->validateToken()`
-
Outputs HTML: `<script>location.href = "/upMVC/";</script>`
-
Headers sent! (any output sends headers)
-
PHP tries: `header("Location: /upMVC/moda")`
-
PHP header ignored! (can't modify headers after output)
-
Browser receives the HTML with JavaScript
-
JavaScript executes: `location.href = "/upMVC/"`
-
Redirects to home ?
PHP Headers and Output
The Golden Rule
You cannot send headers after ANY output!
// ? WRONG - Output before header
echo "Hello";
header("Location: /somewhere"); // Error or ignored!
// ? CORRECT - Header before output
header("Location: /somewhere");
exit;
echo "Hello"; // Never executes
Why This Happens
HTTP protocol requires: HTTP/1.1 200 OK
Content-Type: text/html
Location: /somewhere
? Blank line separates headers from body
<html>...</html> ? Body/output starts here
Once PHP outputs anything (echo, HTML, whitespace), it must send headers first. After that, headers are locked.
What Counts as Output?
-
`echo` or `print`
-
Any HTML outside `<?php ?>`
-
Whitespace before `<?php`
-
Warning/error messages
-
Return from a function that echoes
-
Even a single space!
The Fix
Remove the validateToken() call since we're doing a proper PHP redirect:
// BEFORE (Bug)
$_SESSION['logged'] = true;
$this->html->validateToken(); // ? Outputs JavaScript redirect to home
header("Location: " . $redirectUrl); // ? Ignored!
exit;
// AFTER (Fixed)
$_SESSION['logged'] = true;
// REMOVED validateToken() - was outputting JavaScript that overrode PHP header
header("Location: " . $redirectUrl); // ? Works!
exit;
Why validateToken() Existed
It was probably meant for AJAX logins or as a fallback:
- If headers fail ? JavaScript redirects
- For compatibility with old code
But in modern code:
- ? Use PHP header() redirect
- ? Call exit immediately after
- ? Don't output anything before redirect
- ? Don't use JavaScript redirect as primary method
Testing the Fix
Before All Fixes
[18:22:28] Storing intended_url: /upMVC/moda
[18:22:34] Redirecting to intended: /upMVC/moda
[18:22:34] reqRoute: / ? Wrong!
After All Fixes
[timestamp] Storing intended_url: /upMVC/moda
[timestamp] Redirecting to intended: /upMVC/moda
[timestamp] reqRoute: /moda ? Correct! ?
All 4 Bugs - Complete Summary
Bug #1: Middleware Overwriting Session ?
Line: AuthMiddleware.php:64
Problem: Stored intended_url on every request, overwriting original
Fix: Only store if not already set
Bug #2: Assignment Instead of Comparison ?
Line: Controller.php:68
Problem: if ($logged = true) always true
Fix: Use if ($logged === true)
Bug #3: Missing exit After Redirect ?
Lines: Controller.php:75, 79, 140
Problem: Code continued after header(), sent multiple redirects
Fix: Add exit after every header("Location: ...")
Bug #4: Output Before Header (THIS BUG) ?
Line: Controller.php:117
Problem: validateToken() output JavaScript before PHP header
Fix: Remove validateToken() call before redirect
The Complete Flow (Now Fixed!)
User visits /moda (not logged in)
?
AuthMiddleware:
- Stores: $_SESSION['intended_url'] = '/upMVC/moda'
- Redirects to: /auth
?
User submits login form
?
Controller login():
- Validates credentials ?
- Sets: $_SESSION['logged'] = true ?
- Gets: $url = $_SESSION['intended_url'] = '/upMVC/moda' ?
- Sends: header("Location: /upMVC/moda") ?
- Exits: exit ?
?
Browser receives: HTTP 302 redirect to /upMVC/moda ?
?
Browser requests: /upMVC/moda ?
?
User sees: /moda page ? SUCCESS!
PHP Best Practices Learned
1. Headers Before Output
// ? Always send headers first
header("Content-Type: application/json");
header("Location: /somewhere");
// Then output
echo json_encode($data);
2. Exit After Redirect
// ? Always exit after redirect
header("Location: /target");
exit;
3. No Output in Logic Code
// ? Bad - Mixed logic and output
function login() {
// logic
echo "<script>redirect</script>"; // Output in logic!
header("Location: /"); // Won't work!
}
// ? Good - Separate concerns
function login() {
// pure logic
return '/target-url';
}
// Later, in view layer:
header("Location: " . login());
exit;
4. Check for Output
// Check if headers already sent
if (headers_sent($file, $line)) {
die("Headers already sent in $file on line $line");
}
header("Location: /somewhere");
exit;
Files Changed
-
`d:\GitHub\upMVC\modules\auth\Controller.php`
- Removed `validateToken()` call before redirect
Related Files
-
`d:\GitHub\upMVC\modules\auth\View.php`
- Contains the `validateToken()` method (kept for backward compatibility)
- Can be safely used ONLY when not doing PHP redirects
The Lesson
> Three rules for redirects in PHP:
> 1. No output before header
> 2. Always call exit after redirect header
> 3. Check your View methods - they might output HTML/JavaScript!
This completes the saga of the auth redirect bug! ?
|